mirror of
https://github.com/yeicor-3d/yet-another-cad-viewer.git
synced 2025-12-19 22:24:17 +01:00
playground: minor improvements and install default font
This commit is contained in:
@@ -1543,7 +1543,7 @@ The following npm packages may be included in this product:
|
|||||||
|
|
||||||
- @babel/helper-string-parser@7.27.1
|
- @babel/helper-string-parser@7.27.1
|
||||||
- @babel/helper-validator-identifier@7.27.1
|
- @babel/helper-validator-identifier@7.27.1
|
||||||
- @babel/types@7.28.1
|
- @babel/types@7.28.2
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
@@ -2028,7 +2028,7 @@ SOFTWARE.
|
|||||||
|
|
||||||
The following npm package may be included in this product:
|
The following npm package may be included in this product:
|
||||||
|
|
||||||
- vuetify@3.9.0
|
- vuetify@3.9.2
|
||||||
|
|
||||||
This package contains the following license:
|
This package contains the following license:
|
||||||
|
|
||||||
@@ -2058,16 +2058,16 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
The following npm packages may be included in this product:
|
The following npm packages may be included in this product:
|
||||||
|
|
||||||
- @vue/compiler-core@3.5.17
|
- @vue/compiler-core@3.5.18
|
||||||
- @vue/compiler-dom@3.5.17
|
- @vue/compiler-dom@3.5.18
|
||||||
- @vue/compiler-sfc@3.5.17
|
- @vue/compiler-sfc@3.5.18
|
||||||
- @vue/compiler-ssr@3.5.17
|
- @vue/compiler-ssr@3.5.18
|
||||||
- @vue/reactivity@3.5.17
|
- @vue/reactivity@3.5.18
|
||||||
- @vue/runtime-core@3.5.17
|
- @vue/runtime-core@3.5.18
|
||||||
- @vue/runtime-dom@3.5.17
|
- @vue/runtime-dom@3.5.18
|
||||||
- @vue/server-renderer@3.5.17
|
- @vue/server-renderer@3.5.18
|
||||||
- @vue/shared@3.5.17
|
- @vue/shared@3.5.18
|
||||||
- vue@3.5.17
|
- vue@3.5.18
|
||||||
|
|
||||||
These packages each contain the following license:
|
These packages each contain the following license:
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import {b66Encode} from "./b66.ts";
|
|||||||
import {Base64} from 'js-base64'; // More compatible with binary data from python...
|
import {Base64} from 'js-base64'; // More compatible with binary data from python...
|
||||||
import {NetworkUpdateEvent, NetworkUpdateEventModel} from "../misc/network.ts";
|
import {NetworkUpdateEvent, NetworkUpdateEventModel} from "../misc/network.ts";
|
||||||
import {settings} from "../misc/settings.ts";
|
import {settings} from "../misc/settings.ts";
|
||||||
|
// @ts-expect-error
|
||||||
|
import playgroundStartupCode from './PlaygroundStartup.py?raw';
|
||||||
|
|
||||||
const props = defineProps<{ initialCode: string }>();
|
const props = defineProps<{ initialCode: string }>();
|
||||||
const emit = defineEmits<{ close: [], updateModel: [NetworkUpdateEvent] }>()
|
const emit = defineEmits<{ close: [], updateModel: [NetworkUpdateEvent] }>()
|
||||||
@@ -21,7 +23,7 @@ const emit = defineEmits<{ close: [], updateModel: [NetworkUpdateEvent] }>()
|
|||||||
// ============ LOAD MONACO EDITOR ============
|
// ============ LOAD MONACO EDITOR ============
|
||||||
setupMonaco() // Must be called before using the editor
|
setupMonaco() // Must be called before using the editor
|
||||||
|
|
||||||
const code = ref(props.initialCode); // TODO: Default code as input (and autorun!)
|
const code = ref((import.meta as any)?.hot?.data?.code || props.initialCode);
|
||||||
const outputText = ref(``);
|
const outputText = ref(``);
|
||||||
|
|
||||||
function output(text: string) {
|
function output(text: string) {
|
||||||
@@ -69,13 +71,7 @@ async function setupPyodide() {
|
|||||||
output("Reusing existing Pyodide instance...\n");
|
output("Reusing existing Pyodide instance...\n");
|
||||||
}
|
}
|
||||||
output("Preloading packages...\n");
|
output("Preloading packages...\n");
|
||||||
await pyodideWorker.asyncRun(`import micropip, asyncio
|
await pyodideWorker.asyncRun(playgroundStartupCode, output, output); // Also import yacv_server and mock ocp_vscode here for faster custom code execution
|
||||||
micropip.set_index_urls(["https://yeicor.github.io/OCP.wasm", "https://pypi.org/simple"])
|
|
||||||
await (micropip.install("lib3mf"))
|
|
||||||
micropip.add_mock_package("py-lib3mf", "2.4.1", modules={"py_lib3mf": 'from lib3mf import *'})
|
|
||||||
await (micropip.install(["https://files.pythonhosted.org/packages/67/25/80be117f39ff5652a4fdbd761b061123c5425e379f4b0a5ece4353215d86/yacv_server-0.10.0a4-py3-none-any.whl"]))
|
|
||||||
from yacv_server import *
|
|
||||||
micropip.add_mock_package("ocp-vscode", "2.8.9", modules={"ocp_vscode": 'from yacv_server import *'})`, output, output); // Also import yacv_server and mock ocp_vscode here for faster custom code execution
|
|
||||||
running.value = false; // Indicate that Pyodide is ready
|
running.value = false; // Indicate that Pyodide is ready
|
||||||
output("Pyodide worker initialized.\n");
|
output("Pyodide worker initialized.\n");
|
||||||
}
|
}
|
||||||
@@ -92,6 +88,7 @@ async function runCode() {
|
|||||||
output("Running code...\n");
|
output("Running code...\n");
|
||||||
try {
|
try {
|
||||||
running.value = true;
|
running.value = true;
|
||||||
|
if ((import.meta as any).hot) (import.meta as any).hot.data.code = code.value; // Save code for hot reload
|
||||||
await pyodideWorker.asyncRun(code.value, output, (msg: string) => {
|
await pyodideWorker.asyncRun(code.value, output, (msg: string) => {
|
||||||
// Detect models printed to console (since http server is not available in pyodide)
|
// Detect models printed to console (since http server is not available in pyodide)
|
||||||
if (msg.startsWith(yacvServerModelPrefix)) {
|
if (msg.startsWith(yacvServerModelPrefix)) {
|
||||||
|
|||||||
50
frontend/tools/PlaygroundStartup.py
Normal file
50
frontend/tools/PlaygroundStartup.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import micropip
|
||||||
|
|
||||||
|
# Prioritize the OCP.wasm package repository for finding the ported dependencies.
|
||||||
|
micropip.set_index_urls(["https://yeicor.github.io/OCP.wasm", "https://pypi.org/simple"])
|
||||||
|
|
||||||
|
# For build123d < 0.10.0, we need to install the mock the py-lib3mf package (before the main install).
|
||||||
|
await micropip.install("lib3mf")
|
||||||
|
micropip.add_mock_package("py-lib3mf", "2.4.1", modules={"py_lib3mf": 'from lib3mf import *'})
|
||||||
|
|
||||||
|
# Install the yacv_server package, which is the main server for the OCP.wasm playground; and also preinstalls build123d.
|
||||||
|
await micropip.install("yacv_server")
|
||||||
|
|
||||||
|
# Preimport the yacv_server package to ensure it is available in the global scope, and mock the ocp_vscode package.
|
||||||
|
from yacv_server import *
|
||||||
|
micropip.add_mock_package("ocp-vscode", "2.8.9", modules={"ocp_vscode": 'from yacv_server import *'})
|
||||||
|
show_object = show
|
||||||
|
|
||||||
|
# Preinstall a font to avoid issues with no font being available.
|
||||||
|
def install_font_to_ocp(font_url, font_name=None):
|
||||||
|
# noinspection PyUnresolvedReferences
|
||||||
|
from pyodide.http import pyfetch
|
||||||
|
from OCP.Font import Font_FontMgr, Font_SystemFont, Font_FA_Regular
|
||||||
|
from OCP.TCollection import TCollection_AsciiString
|
||||||
|
import os, asyncio
|
||||||
|
|
||||||
|
font_name = font_name if font_name is not None else font_url.split("/")[-1]
|
||||||
|
|
||||||
|
# Choose a "system-like" font directory
|
||||||
|
font_path = os.path.join("/tmp", font_name)
|
||||||
|
os.makedirs(os.path.dirname(font_path), exist_ok=True)
|
||||||
|
|
||||||
|
# Download the font using pyfetch
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
response = loop.run_until_complete(pyfetch(font_url))
|
||||||
|
font_data = loop.run_until_complete(response.bytes())
|
||||||
|
|
||||||
|
# Save it to the system-like folder
|
||||||
|
with open(font_path, "wb") as f:
|
||||||
|
f.write(font_data)
|
||||||
|
|
||||||
|
mgr = Font_FontMgr.GetInstance_s()
|
||||||
|
font_t = Font_SystemFont(TCollection_AsciiString(font_path))
|
||||||
|
font_t.SetFontPath(Font_FA_Regular, TCollection_AsciiString(font_path))
|
||||||
|
assert mgr.RegisterFont(font_t, False)
|
||||||
|
#print(f"✅ Font installed at: {font_path}")
|
||||||
|
return font_path
|
||||||
|
|
||||||
|
|
||||||
|
# Make sure there is at least one font installed, so that the tests can run
|
||||||
|
install_font_to_ocp("https://raw.githubusercontent.com/xbmc/xbmc/d3a7f95f3f017b8e861d5d95cc4b33eef4286ce2/media/Fonts/arial.ttf")
|
||||||
@@ -1,30 +1,43 @@
|
|||||||
import type {loadPyodide} from "pyodide";
|
import type {loadPyodide} from "pyodide";
|
||||||
|
import type {MessageEventDataIn} from "./pyodide-worker.ts";
|
||||||
|
|
||||||
|
let requestId = 0;
|
||||||
|
|
||||||
/** Simple API for the Pyodide worker. */
|
/** Simple API for the Pyodide worker. */
|
||||||
export function newPyodideWorker(initOpts: Parameters<typeof loadPyodide>[0]) {
|
export function newPyodideWorker(initOpts: Parameters<typeof loadPyodide>[0]) {
|
||||||
let worker = new Worker(new URL('./pyodide-worker.ts', import.meta.url), {type: "module"});
|
let worker = new Worker(new URL('./pyodide-worker.ts', import.meta.url), {type: "module"});
|
||||||
worker.postMessage(initOpts);
|
worker.postMessage(initOpts);
|
||||||
return {
|
const commonRequestResponse = (event: MessageEventDataIn, stdout?: (msg: string) => void, stderr?: (msg: string) => void) => {
|
||||||
asyncRun: (code: String, stdout: (msg: string) => void, stderr: (msg: string) => void) => new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
worker.addEventListener("message", function listener(event) {
|
worker.addEventListener("message", function listener(event: MessageEvent) {
|
||||||
if (event.data?.stdout) {
|
if (stdout && event.data?.stdout) {
|
||||||
stdout(event.data.stdout);
|
stdout(event.data.stdout); // No clue if associated with this request, but we handle it anyway.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.data?.stderr) {
|
if (stderr && event.data?.stderr) {
|
||||||
stderr(event.data.stderr);
|
stderr(event.data.stderr); // No clue if associated with this request, but we handle it anyway.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Result or error.
|
if (event.data?.id !== event.data.id) return; // Ignore messages that are not for this request.
|
||||||
worker.removeEventListener("message", listener);
|
|
||||||
if (event.data?.error) {
|
if (event.data?.error) {
|
||||||
|
worker.removeEventListener("message", listener);
|
||||||
reject(event.data.error);
|
reject(event.data.error);
|
||||||
|
} else if (event.data?.hasOwnProperty("result")) {
|
||||||
|
worker.removeEventListener("message", listener);
|
||||||
|
resolve(event.data.result);
|
||||||
} else {
|
} else {
|
||||||
resolve(event.data?.result);
|
throw new Error("Unexpected message from worker: " + JSON.stringify(event.data));
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
worker.postMessage(code);
|
worker.postMessage(event);
|
||||||
}),
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
asyncRun: (code: string, stdout: (msg: string) => void, stderr: (msg: string) => void) =>
|
||||||
|
commonRequestResponse({type: "asyncRun", id: requestId++, code}, stdout, stderr),
|
||||||
|
mkdirTree: (path: string) => commonRequestResponse({type: "mkdirTree", id: requestId++, path}),
|
||||||
|
writeFile: (path: string, content: string) =>
|
||||||
|
commonRequestResponse({type: "writeFile", id: requestId++, path, content}),
|
||||||
terminate: () => worker.terminate()
|
terminate: () => worker.terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,22 +12,60 @@ let myLoadPyodide = (initOpts: Parameters<typeof loadPyodide>[0]) => loadPyodide
|
|||||||
|
|
||||||
let pyodideReadyPromise: Promise<PyodideInterface> | null = null;
|
let pyodideReadyPromise: Promise<PyodideInterface> | null = null;
|
||||||
|
|
||||||
self.onmessage = async (event: MessageEvent<any>) => {
|
export type MessageEventDataIn = {
|
||||||
|
type: 'asyncRun';
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
} | {
|
||||||
|
type: 'mkdirTree';
|
||||||
|
id: number;
|
||||||
|
path: string;
|
||||||
|
} | {
|
||||||
|
type: 'writeFile';
|
||||||
|
id: number;
|
||||||
|
path: string;
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onmessage = async (event: MessageEvent<MessageEventDataIn>) => {
|
||||||
if (!pyodideReadyPromise) { // First message is always the init message
|
if (!pyodideReadyPromise) { // First message is always the init message
|
||||||
// If we haven't loaded Pyodide yet, do so now.
|
// If we haven't loaded Pyodide yet, do so now.
|
||||||
// This is a singleton, so we only load it once.
|
// This is a singleton, so we only load it once.
|
||||||
pyodideReadyPromise = myLoadPyodide(event.data as Parameters<typeof loadPyodide>[0]);
|
pyodideReadyPromise = myLoadPyodide(event.data as Parameters<typeof loadPyodide>[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// All other messages are code to run.
|
if (event.data.type === 'mkdirTree') {
|
||||||
let code = event.data as string;
|
// Create a directory tree in the Pyodide filesystem.
|
||||||
// make sure loading is done
|
const pyodide = await pyodideReadyPromise;
|
||||||
const pyodide = await pyodideReadyPromise;
|
try {
|
||||||
// Now load any packages we need, run the code, and send the result back.
|
pyodide.FS.mkdirTree(event.data.path);
|
||||||
await pyodide.loadPackagesFromImports(code);
|
self.postMessage({id: event.data.id, result: true});
|
||||||
try {
|
} catch (error: any) {
|
||||||
self.postMessage({result: await pyodide.runPythonAsync(code)});
|
self.postMessage({id: event.data.id, error: error.message});
|
||||||
} catch (error: any) {
|
}
|
||||||
self.postMessage({error: error.message});
|
return;
|
||||||
|
} else if (event.data.type === 'writeFile') {
|
||||||
|
// Write a file to the Pyodide filesystem.
|
||||||
|
const pyodide = await pyodideReadyPromise;
|
||||||
|
try {
|
||||||
|
pyodide.FS.writeFile(event.data.path, event.data.content);
|
||||||
|
self.postMessage({id: event.data.id, result: true});
|
||||||
|
} catch (error: any) {
|
||||||
|
self.postMessage({id: event.data.id, error: error.message});
|
||||||
|
}
|
||||||
|
} else if (event.data.type === 'asyncRun') {
|
||||||
|
let code = event.data.code;
|
||||||
|
// make sure loading is done
|
||||||
|
const pyodide = await pyodideReadyPromise;
|
||||||
|
// Now load any packages we need, run the code, and send the result back.
|
||||||
|
await pyodide.loadPackagesFromImports(code);
|
||||||
|
try {
|
||||||
|
self.postMessage({id: event.data.id, result: await pyodide.runPythonAsync(code)});
|
||||||
|
} catch (error: any) {
|
||||||
|
self.postMessage({id: event.data.id, error: error.message});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.error("Unknown message type:", (event.data as any)?.type);
|
||||||
|
self.postMessage({id: (event.data as any)?.id, error: "Unknown message type: " + (event.data as any)?.type});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user