playground: most of the logic for both frontend and backend is implemented, some bugs remain

This commit is contained in:
Yeicor
2025-07-20 21:35:45 +02:00
parent 0460e939e4
commit a63d018850
22 changed files with 617 additions and 165 deletions

View File

@@ -40,9 +40,9 @@ jobs:
with: with:
python-version: "3.12" python-version: "3.12"
cache: "poetry" cache: "poetry"
- run: "SKIP_BUILD_FRONTEND=true poetry lock" - run: "poetry lock"
- run: "SKIP_BUILD_FRONTEND=true poetry install" - run: "poetry install"
- run: "SKIP_BUILD_FRONTEND=true poetry build" - run: "poetry build" # Skips building frontend (not using task)
build-logo: build-logo:
name: "Build logo" name: "Build logo"
@@ -56,8 +56,8 @@ jobs:
with: with:
python-version: "3.12" python-version: "3.12"
cache: "poetry" cache: "poetry"
- run: "SKIP_BUILD_FRONTEND=true poetry lock" - run: "poetry lock"
- run: "SKIP_BUILD_FRONTEND=true poetry install" - run: "poetry install"
- run: "poetry run python yacv_server/logo.py" - run: "poetry run python yacv_server/logo.py"
- uses: "actions/upload-artifact@v4" - uses: "actions/upload-artifact@v4"
with: with:
@@ -77,8 +77,8 @@ jobs:
with: with:
python-version: "3.12" python-version: "3.12"
cache: "poetry" cache: "poetry"
- run: "SKIP_BUILD_FRONTEND=true poetry lock" - run: "poetry lock"
- run: "SKIP_BUILD_FRONTEND=true poetry install" - run: "poetry install"
- run: "YACV_DISABLE_SERVER=true poetry run python example/object.py" - run: "YACV_DISABLE_SERVER=true poetry run python example/object.py"
- uses: "actions/upload-artifact@v4" - uses: "actions/upload-artifact@v4"
with: with:

View File

@@ -35,11 +35,7 @@ jobs:
- uses: "actions/download-artifact@v4" - uses: "actions/download-artifact@v4"
with: # Downloads all artifacts from the build job with: # Downloads all artifacts from the build job
path: "./public" path: "./public"
- run: | # Merge the subdirectories of public into a single directory merge-multiple: true
for dir in public/*; do
mv "$dir/"* public/
rmdir "$dir"
done
- uses: "actions/configure-pages@v5" - uses: "actions/configure-pages@v5"
- uses: "actions/upload-pages-artifact@v3" - uses: "actions/upload-pages-artifact@v3"
with: with:
@@ -71,5 +67,6 @@ jobs:
cache: "poetry" cache: "poetry"
- run: "poetry install" - run: "poetry install"
- run: "poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}" - run: "poetry config pypi-token.pypi ${{ secrets.PYPI_TOKEN }}"
- run: "poetry publish --build" - run: "poetry run task build" # This task also builds the frontend (with reduced features for less size)
- run: "poetry publish"

View File

@@ -761,6 +761,36 @@ MIT License
----------- -----------
The following npm package may be included in this product:
- pako@2.1.0
This package contains the following license:
(The MIT License)
Copyright (C) 2014-2017 by Vitaly Puzrin and Andrei Tuputcyn
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-----------
The following npm package may be included in this product: The following npm package may be included in this product:
- lie@3.3.0 - lie@3.3.0

View File

@@ -1,9 +0,0 @@
import os
import subprocess
if __name__ == "__main__":
# Building the frontend is optional
if os.getenv('SKIP_BUILD_FRONTEND') is None and os.path.exists('package.json'):
# When building the backend, make sure the frontend is built first
subprocess.run(['yarn', 'install'], check=True)
subprocess.run(['yarn', 'build', '--outDir', 'yacv_server/frontend'], check=True)

View File

@@ -13,6 +13,7 @@ import {Document} from "@gltf-transform/core";
import type ModelViewerWrapperT from "./viewer/ModelViewerWrapper.vue"; import type ModelViewerWrapperT from "./viewer/ModelViewerWrapper.vue";
import {mdiPlus} from '@mdi/js' import {mdiPlus} from '@mdi/js'
import SvgIcon from '@jamescoyle/vue-icon'; import SvgIcon from '@jamescoyle/vue-icon';
import {toBuffer} from "./misc/gltf.ts";
// NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously // NOTE: The ModelViewer library is big (THREE.js), so we split it and import it asynchronously
const ModelViewerWrapper = defineAsyncComponent({ const ModelViewerWrapper = defineAsyncComponent({
@@ -82,14 +83,24 @@ networkMgr.addEventListener('update-early',
networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUpdateEvent)); networkMgr.addEventListener('update', (e) => onModelUpdateRequest(e as NetworkUpdateEvent));
(async () => { // Start loading all configured models ASAP (async () => { // Start loading all configured models ASAP
let sett = await settings(); let sett = await settings();
watch(viewer, (newViewer) => { if (sett.preload.length > 0) {
if (newViewer) { watch(viewer, (newViewer) => {
newViewer.setPosterText('<tspan x="50%" dy="1.2em">Trying to load' + if (newViewer) {
' models from:</tspan>' + sett.preload.map((url: string) => '<tspan x="50%" dy="1.2em">- ' + url + '</tspan>').join("")); newViewer.setPosterText('<tspan x="50%" dy="1.2em">Trying to load' +
' models from:</tspan>' + sett.preload.map((url: string) => '<tspan x="50%" dy="1.2em">- ' + url + '</tspan>').join(""));
}
});
for (let model of sett.preload) {
await networkMgr.load(model);
} }
}); } else { // Skip to interface without models (useful for playground mode)
for (let model of sett.preload) { console.debug("Showing empty gltf document to load the interface without models.");
await networkMgr.load(model); // FIXME: Empty document breaks the playground-loaded models (using preload seems to fix this, maybe __helpers issue?)
let emptyDoc = new Document();
emptyDoc.createScene();
let buffer = await toBuffer(emptyDoc);
let blob = new Blob([buffer], {type: 'model/gltf-binary'});
sceneUrl.value = URL.createObjectURL(blob);
} }
})(); })();
@@ -117,7 +128,7 @@ async function loadModelManual() {
<svg-icon :path="mdiPlus" type="mdi"/> <svg-icon :path="mdiPlus" type="mdi"/>
</v-btn> </v-btn>
</template> </template>
<models ref="models" :viewer="viewer" @remove="onModelRemoveRequest"/> <models ref="models" :viewer="viewer" @remove-model="onModelRemoveRequest"/>
</sidebar> </sidebar>
<!-- The right collapsible sidebar has the list of tools --> <!-- The right collapsible sidebar has the list of tools -->
@@ -125,7 +136,7 @@ async function loadModelManual() {
<template #toolbar> <template #toolbar>
<v-toolbar-title>Tools</v-toolbar-title> <v-toolbar-title>Tools</v-toolbar-title>
</template> </template>
<tools ref="tools" :viewer="viewer" @findModel="(name) => models?.findModel(name)"/> <tools ref="tools" :viewer="viewer" @find-model="models?.findModel" @update-model="onModelUpdateRequest"/>
</sidebar> </sidebar>
</v-layout> </v-layout>
@@ -135,6 +146,6 @@ async function loadModelManual() {
<style> <style>
html, body { html, body {
height: 100%; height: 100%;
overflow: hidden; overflow: hidden !important;
} }
</style> </style>

View File

@@ -0,0 +1,30 @@
<script setup lang="ts">
import {mdiLockQuestion} from "@mdi/js";
import {VBtn, VTooltip} from "vuetify/lib/components/index.mjs";
import SvgIcon from "@jamescoyle/vue-icon";
// @ts-expect-error
let isSmallBuild = typeof __YACV_SMALL_BUILD__ !== 'undefined' && __YACV_SMALL_BUILD__;
function clickedButton() { // Redirect to the main build
window.open("https://yeicor-3d.github.io/yet-another-cad-viewer/" + window.location.search + window.location.hash, '_blank');
}
</script>
<template>
<!-- @ts-ignore-->
<!-- Include the children as this is a full build -->
<slot v-if="!isSmallBuild"/>
<!-- A small info button saying that a feature is missing, and linking to the main build -->
<v-btn v-else icon @click="clickedButton" base-color="#a00" style="margin: auto; display: block;">
<v-tooltip activator="parent">
This feature is not available in the small build.<br/>
Click to go to the main build.
</v-tooltip>
<svg-icon :path="mdiLockQuestion" type="mdi"/>
</v-btn>
</template>
<style scoped>
</style>

View File

@@ -5,6 +5,9 @@ let io = new WebIO();
export let extrasNameKey = "__yacv_name"; export let extrasNameKey = "__yacv_name";
export let extrasNameValueHelpers = "__helpers"; export let extrasNameValueHelpers = "__helpers";
// @ts-expect-error
let isSmallBuild = typeof __YACV_SMALL_BUILD__ !== 'undefined' && __YACV_SMALL_BUILD__;
/** /**
* Loads a GLB model from a URL and adds it to the document or replaces it if the names match. * Loads a GLB model from a URL and adds it to the document or replaces it if the names match.
* *
@@ -27,7 +30,7 @@ export async function mergePartial(url: string, name: string, document: Document
try { // Try to load fast if no extensions are used try { // Try to load fast if no extensions are used
newDoc = await io.readBinary(new Uint8Array(buffer)); newDoc = await io.readBinary(new Uint8Array(buffer));
} catch (e) { // Fallback to wait for download and register big extensions } catch (e) { // Fallback to wait for download and register big extensions
if (e instanceof Error && e.message.toLowerCase().includes("khr_draco_mesh_compression")) { if (!isSmallBuild && e instanceof Error && e.message.toLowerCase().includes("khr_draco_mesh_compression")) {
if (alreadyTried["draco"]) throw e; else alreadyTried["draco"] = true; if (alreadyTried["draco"]) throw e; else alreadyTried["draco"] = true;
// WARNING: Draco decompression on web is really slow for non-trivial models! (it should work?) // WARNING: Draco decompression on web is really slow for non-trivial models! (it should work?)
let {KHRDracoMeshCompression} = await import("@gltf-transform/extensions") let {KHRDracoMeshCompression} = await import("@gltf-transform/extensions")
@@ -38,7 +41,7 @@ export async function mergePartial(url: string, name: string, document: Document
'draco3d.decoder': await dracoDecoderWeb.default({}), 'draco3d.decoder': await dracoDecoderWeb.default({}),
'draco3d.encoder': await dracoEncoderWeb.default({}) 'draco3d.encoder': await dracoEncoderWeb.default({})
}); });
} else if (e instanceof Error && e.message.toLowerCase().includes("ext_texture_webp")) { } else if (!isSmallBuild && e instanceof Error && e.message.toLowerCase().includes("ext_texture_webp")) {
if (alreadyTried["webp"]) throw e; else alreadyTried["webp"] = true; if (alreadyTried["webp"]) throw e; else alreadyTried["webp"] = true;
let {EXTTextureWebP} = await import("@gltf-transform/extensions") let {EXTTextureWebP} = await import("@gltf-transform/extensions")
io.registerExtensions([EXTTextureWebP]); io.registerExtensions([EXTTextureWebP]);

View File

@@ -1,4 +1,7 @@
// These are the default values for the settings, which are overridden below // These are the default values for the settings, which are overridden below
import {ungzip} from "pako";
import {b66Decode} from "../tools/b66.ts";
let settingsCache: any = null; let settingsCache: any = null;
export async function settings() { export async function settings() {
@@ -49,10 +52,23 @@ export async function settings() {
if (key in settings) (settings as any)[key] = parseSetting(key, value, settings); if (key in settings) (settings as any)[key] = parseSetting(key, value, settings);
}) })
// Auto-decompress the code
if (settings.code.length > 0) {
try {
settings.code = ungzip(b66Decode(settings.code), {to: 'string'});
} catch (error) {
console.warn("Failed to decompress code (assuming raw code):", error);
}
}
// Get the default preload URL if not overridden (requires a fetch that is avoided if possible) // Get the default preload URL if not overridden (requires a fetch that is avoided if possible)
for (let i = 0; i < settings.preload.length; i++) { for (let i = 0; i < settings.preload.length; i++) {
let url = settings.preload[i]; let url = settings.preload[i];
if (url === '<auto>') { if (url === '<auto>') {
if (settings.code != "") { // <auto> means no preload URL if code is set
settings.preload = settings.preload.slice(0, i).concat(settings.preload.slice(i + 1));
continue; // Skip this preload URL
}
const possibleBackend = new URL("./?api_updates=true", window.location.href) const possibleBackend = new URL("./?api_updates=true", window.location.href)
await fetch(possibleBackend, {method: "HEAD"}).then((response) => { await fetch(possibleBackend, {method: "HEAD"}).then((response) => {
if (response.ok && response.headers.get("Content-Type") === "text/event-stream") { if (response.ok && response.headers.get("Content-Type") === "text/event-stream") {

View File

@@ -7,7 +7,7 @@ import Model from "./Model.vue";
import {inject, ref, type Ref} from "vue"; import {inject, ref, type Ref} from "vue";
const props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>(); const props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>();
const emit = defineEmits<{ remove: [string] }>() const emit = defineEmits<{ removeModel: [string] }>()
let {sceneDocument} = inject<{ sceneDocument: Ref<Document> }>('sceneDocument')!!; let {sceneDocument} = inject<{ sceneDocument: Ref<Document> }>('sceneDocument')!!;
@@ -32,7 +32,7 @@ function meshName(mesh: Mesh) {
} }
function onRemove(mesh: Mesh) { function onRemove(mesh: Mesh) {
emit('remove', meshName(mesh)) emit('removeModel', meshName(mesh))
} }
function findModel(name: string) { function findModel(name: string) {

View File

@@ -1,15 +1,20 @@
<script setup lang="ts"> <script setup lang="ts">
import {setupMonaco} from "./monaco.ts"; import {setupMonaco} from "./monaco.ts";
import {VueMonacoEditor} from '@guolao/vue-monaco-editor' import {VueMonacoEditor} from '@guolao/vue-monaco-editor'
import {ref, shallowRef} from "vue"; import {nextTick, ref, shallowRef} from "vue";
import Loading from "../misc/Loading.vue"; import Loading from "../misc/Loading.vue";
import {asyncRun, pyodideWorker, resetState} from "./pyodide-worker-api.ts"; import {newPyodideWorker} from "./pyodide-worker-api.ts";
import {mdiCircleOpacity, mdiClose, mdiLockReset, mdiPlay, mdiReload, mdiRun} from "@mdi/js"; import {mdiCircleOpacity, mdiClose, mdiPlay, mdiReload, mdiShare} from "@mdi/js";
import {VBtn, VCard, VCardText, VSlider, VSpacer, VToolbar, VToolbarTitle} from "vuetify/components"; import {VBtn, VCard, VCardText, VSlider, VSpacer, VToolbar, VToolbarTitle} from "vuetify/components";
import SvgIcon from '@jamescoyle/vue-icon'; import SvgIcon from '@jamescoyle/vue-icon';
import {version as pyodideVersion} from "pyodide";
import {gzip} from 'pako';
import {b66Encode} from "./b66.ts";
import {Base64} from 'js-base64'; // More compatible with binary data from python...
import {NetworkUpdateEvent, NetworkUpdateEventModel} from "../misc/network.ts";
const props = defineProps<{ initialCode: string }>(); const props = defineProps<{ initialCode: string }>();
const emit = defineEmits<{ (e: 'close'): void }>() 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
@@ -20,12 +25,12 @@ const outputText = ref(``);
function output(text: string) { function output(text: string) {
outputText.value += text; // Append to output outputText.value += text; // Append to output
console.log(text); // Also log to console console.log(text); // Also log to console
setTimeout(() => { // Scroll to bottom? TODO: Test nextTick(() => { // Scroll to bottom
const consoleElement = document.querySelector('.playground-console pre'); const consoleElement = document.querySelector('.playground-console');
if (consoleElement) { if (consoleElement) {
consoleElement.scrollTop = consoleElement.scrollHeight; consoleElement.scrollTop = consoleElement.scrollHeight;
} }
}, 0); })
} }
const MONACO_EDITOR_OPTIONS = { const MONACO_EDITOR_OPTIONS = {
@@ -40,45 +45,120 @@ const handleMount = (editorInstance: typeof VueMonacoEditor) => (editor.value =
const opacity = ref(0.9); // Opacity for the editor const opacity = ref(0.9); // Opacity for the editor
// ============ LOAD PYODIDE (ASYNC) ============ // ============ LOAD PYODIDE (ASYNC) ============
let pyodideWorker: ReturnType<typeof newPyodideWorker> | null = (import.meta as any).hot?.data?.pyodideWorker || null;
const running = ref(true); const running = ref(true);
async function setupPyodide() { async function setupPyodide() {
running.value = true;
if (opacity.value == 0.0) opacity.value = 0.9; // User doesn't know how to show code again, reset after reopening if (opacity.value == 0.0) opacity.value = 0.9; // User doesn't know how to show code again, reset after reopening
let firstTime = pyodideWorker === null; if (pyodideWorker === null) {
if (firstTime) { output("Creating new Pyodide worker...\n");
resetState(); pyodideWorker = newPyodideWorker({
output("Loading packages...\n"); indexURL: `https://cdn.jsdelivr.net/pyodide/v${pyodideVersion}/full/`, // FIXME: Local deployment?
await asyncRun(`import micropip, asyncio packages: ["micropip", "sqlite3"], // Faster load if done here
micropip.set_index_urls(["https://yeicor.github.io/OCP.wasm", "https://pypi.org/simple"]) });
await (micropip.install("lib3mf")) if ((import.meta as any).hot) (import.meta as any).hot.data.pyodideWorker = pyodideWorker
micropip.add_mock_package("py-lib3mf", "2.4.1", modules={"py_lib3mf": '''from lib3mf import *'''})
await (micropip.install(["build123d"]))`, output);
if (props.initialCode != "") {
await runCode();
opacity.value = 0.0; // Hide editor after running initial code
} else {
output("Ready for custom code.\n");
}
} else { } else {
output("Reusing existing Pyodide instance...\n"); output("Reusing existing Pyodide instance...\n");
} }
output("Preloading packages...\n");
await pyodideWorker.asyncRun(`import micropip, asyncio
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 *`, output, output); // Also import yacv_server 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");
} }
setupPyodide()
async function runCode() { async function runCode() {
if (pyodideWorker === null) {
output("Pyodide worker is not initialized. Please wait...\n");
return;
}
if (running.value) {
output("Pyodide is already running. Please wait...\n");
return;
}
output("Running code...\n"); output("Running code...\n");
try { try {
running.value = true; running.value = true;
await asyncRun(code.value, output); await pyodideWorker.asyncRun(code.value, output, (msg: string) => {
// Detect models printed to console (since http server is not available in pyodide)
if (msg.startsWith(yacvServerModelPrefix)) {
const modelData = msg.slice(yacvServerModelPrefix.length);
onModelData(modelData);
} else {
output(msg); // Print other messages directly
}
});
} catch (e) { } catch (e) {
output(`Error running initial code: ${e}\n`); output(`Error running initial code: ${e}\n`);
} finally { } finally {
running.value = false; // Indicate that Pyodide is ready running.value = false; // Indicate that Pyodide is ready
} }
} }
const yacvServerModelPrefix = "yacv_server://model/";
function onModelData(modelData: string) {
output(`Model data detected... ${modelData.length}B\n`);
// Decode the model data and emit the event for the interface to handle
// - Start by finding the end of the initial json object by looking for brackets.
let i = 0;
let openBrackets = 0;
for (; i < modelData.length; i++) {
if (modelData[i] === '{') openBrackets++;
else if (modelData[i] === '}') openBrackets--;
if (openBrackets === 0) break; // Found the end of the JSON object
}
if (openBrackets !== 0) throw `Error: Invalid model data received: ${modelData}\n`
const jsonData = modelData.slice(0, i + 1); // Extract the JSON part and parse it into the proper class
let modelMetadataRaw = JSON.parse(jsonData);
const modelMetadata: any = new NetworkUpdateEventModel(modelMetadataRaw.name, "Wait for it...", modelMetadataRaw.hash, modelMetadataRaw.is_remove)
// console.debug(`Model metadata:`, modelMetadata);
output(`Model metadata: ${JSON.stringify(modelMetadata)}\n`);
// - Now decode the rest of the model data which is a single base64 encoded glb file (or an empty string)
if (!modelMetadata.isRemove) {
const binaryData = Base64.toUint8Array(modelData.slice(i + 1)); // Extract the base64 part
console.assert(binaryData.slice(0, 4).toString() == "103,108,84,70", // Ugly...
"Invalid GLTF binary data received: " + binaryData.slice(0, 4).toString());
// - Create a Blob from the binary data to be used as a URL
const blob = new Blob([binaryData], {type: 'model/gltf-binary'});
modelMetadata.url = URL.createObjectURL(blob); // Set the hacked URL in the model metadata
}
// - Emit the event with the model metadata and URL
let networkUpdateEvent = new NetworkUpdateEvent([modelMetadata], () => {
});
emit('updateModel', networkUpdateEvent);
}
function resetWorker() {
code.value = props.initialCode; // Reset code to initial state
if (pyodideWorker) {
pyodideWorker.terminate(); // Terminate existing worker
pyodideWorker = null; // Reset worker reference
}
outputText.value = ``; // Clear output text
setupPyodide(); // Reinitialize Pyodide
}
function shareLink() {
const baseUrl = window.location
const urlParams = new URLSearchParams(baseUrl.search); // Keep all previous URL parameters
urlParams.set('code', b66Encode(gzip(code.value, {level: 9}))); // Compress and encode the code
const shareUrl = `${baseUrl.origin}${baseUrl.pathname}?${urlParams.toString()}`;
output(`Share link ready: ${shareUrl}\n`)
navigator.clipboard.writeText(shareUrl)
.then(() => output("Link copied to clipboard!\n"))
.catch(err => output(`Failed to copy link: ${err}\n`));
}
const reused = (import.meta as any).hot?.data?.pyodideWorker !== undefined;
setupPyodide().then(() => {
if (props.initialCode != "" && !reused) runCode();
});
</script> </script>
<template> <template>
@@ -94,7 +174,7 @@ async function runCode() {
<!-- TODO: snapshots... --> <!-- TODO: snapshots... -->
<v-btn icon @click="resetState()"> <v-btn icon @click="resetWorker()">
<svg-icon :path="mdiReload" type="mdi"/> <svg-icon :path="mdiReload" type="mdi"/>
</v-btn> </v-btn>
@@ -102,6 +182,10 @@ async function runCode() {
<svg-icon :path="mdiPlay" type="mdi"/> <svg-icon :path="mdiPlay" type="mdi"/>
</v-btn> </v-btn>
<v-btn icon @click="shareLink()">
<svg-icon :path="mdiShare" type="mdi"/>
</v-btn>
<v-btn icon @click="emit('close')"> <v-btn icon @click="emit('close')">
<svg-icon :path="mdiClose" type="mdi"/> <svg-icon :path="mdiClose" type="mdi"/>
</v-btn> </v-btn>
@@ -110,13 +194,8 @@ async function runCode() {
<!-- Only show content if opacity is greater than 0 --> <!-- Only show content if opacity is greater than 0 -->
<div class="playground-container"> <div class="playground-container">
<div class="playground-editor"> <div class="playground-editor">
<VueMonacoEditor <VueMonacoEditor v-model:value="code" :theme="editorTheme" :options="MONACO_EDITOR_OPTIONS"
v-model:value="code" language="python" @mount="handleMount"/>
:theme="editorTheme"
:options="MONACO_EDITOR_OPTIONS"
language="python"
@mount="handleMount"
/>
</div> </div>
<div class="playground-console"> <div class="playground-console">
<h3>Console Output</h3> <h3>Console Output</h3>

View File

@@ -21,6 +21,8 @@ import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
import {defineAsyncComponent, ref, type Ref} from "vue"; import {defineAsyncComponent, ref, type Ref} from "vue";
import type {SelectionInfo} from "./selection"; import type {SelectionInfo} from "./selection";
import {settings} from "../misc/settings.ts"; import {settings} from "../misc/settings.ts";
import type {NetworkUpdateEvent} from "../misc/network.ts";
import IfNotSmallBuild from "../misc/IfNotSmallBuild.vue";
const SelectionComponent = defineAsyncComponent({ const SelectionComponent = defineAsyncComponent({
loader: () => import("./Selection.vue"), loader: () => import("./Selection.vue"),
@@ -43,7 +45,7 @@ const PlaygroundDialogContent = defineAsyncComponent({
let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>(); let props = defineProps<{ viewer: InstanceType<typeof ModelViewerWrapper> | null }>();
const emit = defineEmits<{ findModel: [string] }>() const emit = defineEmits<{ findModel: [string], updateModel: [NetworkUpdateEvent] }>()
const sett = ref<any | null>(null); const sett = ref<any | null>(null);
const showPlaygroundDialog = ref(false); const showPlaygroundDialog = ref(false);
@@ -162,14 +164,17 @@ window.addEventListener('keydown', (event) => {
<h5>Extras</h5> <h5>Extras</h5>
<v-dialog v-model="showPlaygroundDialog" persistent :scrim="false" attach="body"> <v-dialog v-model="showPlaygroundDialog" persistent :scrim="false" attach="body">
<template v-slot:activator="{ props }"> <template v-slot:activator="{ props }">
<v-btn icon v-bind="props" style="width: 100%"> <v-btn v-bind="props" style="width: 100%">
<v-tooltip activator="parent">Open a python editor and build models directly in the browser!</v-tooltip> <v-tooltip activator="parent">Open a python editor and build models directly in the browser!</v-tooltip>
<svg-icon :path="mdiScriptTextPlay" type="mdi"/> <svg-icon :path="mdiScriptTextPlay" type="mdi"/>
&nbsp;Sandbox &nbsp;Sandbox
</v-btn> </v-btn>
</template> </template>
<template v-slot:default="{ isActive }"> <template v-slot:default="{ isActive }">
<playground-dialog-content v-if="sett != null" :initial-code="sett.value.code" @close="isActive.value = false"/> <if-not-small-build>
<playground-dialog-content v-if="sett != null" :initial-code="sett.code" @close="isActive.value = false"
@update-model="(event: NetworkUpdateEvent) => emit('updateModel', event)"/>
</if-not-small-build>
</template> </template>
</v-dialog> </v-dialog>
<v-btn icon @click="downloadSceneGlb"> <v-btn icon @click="downloadSceneGlb">

54
frontend/tools/b66.ts Normal file
View File

@@ -0,0 +1,54 @@
// B66 encoding and decoding functions for compact url query parameter values. https://gist.github.com/danneu/6755394
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.-_~";
export function b66Encode(data: Uint8Array): string {
let result = "";
let bits = 0;
let value = 0;
for (let byte of data) {
value = (value << 8) | byte;
bits += 8;
while (bits >= 6) {
bits -= 6;
result += alphabet[(value >> bits) & 0x3F];
}
}
if (bits > 0) {
result += alphabet[(value << (6 - bits)) & 0x3F];
}
return result;
}
export function b66Decode(encoded: string): Uint8Array {
let result = [];
let bits = 0;
let value = 0;
for (let char of encoded) {
const index = alphabet.indexOf(char);
if (index === -1) {
throw new Error(`Invalid character '${char}' in B66 encoded string.`);
}
value = (value << 6) | index;
bits += 6;
while (bits >= 8) {
bits -= 8;
result.push((value >> bits) & 0xFF);
}
}
if (bits > 0) {
// If there are leftover bits, they should not be present in a valid B66 encoding.
if (value << (8 - bits)) {
throw new Error("Invalid B66 encoding: leftover bits.");
}
}
return new Uint8Array(result);
}

View File

@@ -1,44 +1,31 @@
// Each message needs a unique id to identify the response. In a real example, import type {loadPyodide} from "pyodide";
// we might use a real uuid package
let lastId = 1;
function getId() { /** Simple API for the Pyodide worker. */
return lastId++; export function newPyodideWorker(initOpts: Parameters<typeof loadPyodide>[0]) {
let worker = new Worker(new URL('./pyodide-worker.ts', import.meta.url), {type: "module"});
worker.postMessage(initOpts);
return {
asyncRun: (code: String, stdout: (msg: string) => void, stderr: (msg: string) => void) => new Promise((resolve, reject) => {
worker.addEventListener("message", function listener(event) {
if (event.data?.stdout) {
stdout(event.data.stdout);
return;
}
if (event.data?.stderr) {
stderr(event.data.stderr);
return;
}
// Result or error.
worker.removeEventListener("message", listener);
if (event.data?.error) {
reject(event.data.error);
} else {
resolve(event.data?.result);
}
});
worker.postMessage(code);
}),
terminate: () => worker.terminate()
}
} }
// Add an id to msg, send it to worker, then wait for a response with the same id.
// When we get such a response, use it to resolve the promise.
function requestResponse(worker: Worker, code: String, output: (msg: string) => void) {
return new Promise((resolve) => {
const idWorker = getId();
worker.addEventListener("message", function listener(event) {
if (event.data?.stdout) {
output(event.data.stdout + "\n");
return;
}
if (event.data?.stderr) {
output(event.data.stderr + "\n");
return;
}
if (event.data?.id !== idWorker) return;
// This listener is done so remove it.
worker.removeEventListener("message", listener);
// Filter the id out of the result
const {id, ...rest} = event.data;
resolve(rest);
});
worker.postMessage({id: idWorker, code});
});
}
export function asyncRun(code: String, output: (msg: string) => void) {
return requestResponse(pyodideWorker as Worker, code, output);
}
export function resetState() {
// Reset the worker state by terminating it and creating a new one.
if (pyodideWorker) pyodideWorker.terminate();
pyodideWorker = new Worker(new URL('./pyodide-worker.ts', import.meta.url), {type: "module"});
}
export let pyodideWorker: Worker | null = null;

View File

@@ -1,27 +1,33 @@
import {loadPyodide, version} from "pyodide"; import {loadPyodide, type PyodideInterface} from "pyodide";
let pyodideReadyPromise = loadPyodide({ let myLoadPyodide = (initOpts: Parameters<typeof loadPyodide>[0]) => loadPyodide({
indexURL: `https://cdn.jsdelivr.net/pyodide/v${version}/full/`, // FIXME: Local deployment? ...initOpts,
packages: ["micropip", "sqlite3"], // Preloaded faster here... stdout: (msg) => self.postMessage({stdout: msg + "\n"}), // Add newline for better readability
stdout: (msg) => self.postMessage({stdout: msg}), stderr: (msg) => self.postMessage({stderr: msg + "\n"}), // Add newline for better readability
stderr: (msg) => self.postMessage({stderr: msg}),
stdin: () => { stdin: () => {
console.warn("Input requested by Python code, but stdin is not supported in this playground."); console.warn("Input requested by Python code, but stdin is not supported in this playground.");
return ""; return "";
}, },
}); });
self.onmessage = async (event) => { let pyodideReadyPromise: Promise<PyodideInterface> | null = null;
self.onmessage = async (event: MessageEvent<any>) => {
if (!pyodideReadyPromise) { // First message is always the init message
// If we haven't loaded Pyodide yet, do so now.
// This is a singleton, so we only load it once.
pyodideReadyPromise = myLoadPyodide(event.data as Parameters<typeof loadPyodide>[0]);
return;
}
// All other messages are code to run.
let code = event.data as string;
// make sure loading is done // make sure loading is done
const pyodide = await pyodideReadyPromise; const pyodide = await pyodideReadyPromise;
const {id, code} = event.data;
// Now load any packages we need, run the code, and send the result back. // Now load any packages we need, run the code, and send the result back.
await pyodide.loadPackagesFromImports(code); await pyodide.loadPackagesFromImports(code);
try { try {
// Execute the python code in this context self.postMessage({result: await pyodide.runPythonAsync(code)});
const result = await pyodide.runPythonAsync(code);
self.postMessage({result, id});
} catch (error: any) { } catch (error: any) {
self.postMessage({error: error.message, id}); self.postMessage({error: error.message});
} }
}; };

View File

@@ -23,7 +23,9 @@
"@jamescoyle/vue-icon": "^0.1.2", "@jamescoyle/vue-icon": "^0.1.2",
"@mdi/js": "^7.4.47", "@mdi/js": "^7.4.47",
"@mdi/svg": "^7.4.47", "@mdi/svg": "^7.4.47",
"js-base64": "^3.7.7",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.52.2",
"pako": "^2.1.0",
"pyodide": "^0.28.0", "pyodide": "^0.28.0",
"three": "^0.178.0", "three": "^0.178.0",
"three-mesh-bvh": "^0.9.0", "three-mesh-bvh": "^0.9.0",
@@ -34,6 +36,7 @@
"devDependencies": { "devDependencies": {
"@tsconfig/node20": "^20.1.4", "@tsconfig/node20": "^20.1.4",
"@types/node": "^22.9.3", "@types/node": "^22.9.3",
"@types/pako": "^2.0.3",
"@types/three": "^0.178.0", "@types/three": "^0.178.0",
"@vitejs/plugin-vue": "^6.0.0", "@vitejs/plugin-vue": "^6.0.0",
"@vitejs/plugin-vue-jsx": "^5.0.0", "@vitejs/plugin-vue-jsx": "^5.0.0",
@@ -45,7 +48,7 @@
"terser": "^5.36.0", "terser": "^5.36.0",
"typescript": "~5.8.0", "typescript": "~5.8.0",
"vite": "^7.0.0", "vite": "^7.0.0",
"vue-tsc": "^3.0.0", "vite-plugin-static-copy": "^3.1.1",
"vite-plugin-static-copy": "^3.1.1" "vue-tsc": "^3.0.0"
} }
} }

194
poetry.lock generated
View File

@@ -1,4 +1,4 @@
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand. # This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand.
[[package]] [[package]]
name = "anytree" name = "anytree"
@@ -6,6 +6,7 @@ version = "2.13.0"
description = "Powerful and Lightweight Python Tree Data Structure with various plugins" description = "Powerful and Lightweight Python Tree Data Structure with various plugins"
optional = false optional = false
python-versions = "<4.0,>=3.9.2" python-versions = "<4.0,>=3.9.2"
groups = ["main"]
files = [ files = [
{file = "anytree-2.13.0-py3-none-any.whl", hash = "sha256:4cbcf10df36b1f1cba131b7e487ff3edafc9d6e932a3c70071b5b768bab901ff"}, {file = "anytree-2.13.0-py3-none-any.whl", hash = "sha256:4cbcf10df36b1f1cba131b7e487ff3edafc9d6e932a3c70071b5b768bab901ff"},
{file = "anytree-2.13.0.tar.gz", hash = "sha256:c9d3aa6825fdd06af7ebb05b4ef291d2db63e62bb1f9b7d9b71354be9d362714"}, {file = "anytree-2.13.0.tar.gz", hash = "sha256:c9d3aa6825fdd06af7ebb05b4ef291d2db63e62bb1f9b7d9b71354be9d362714"},
@@ -17,6 +18,7 @@ version = "3.0.0"
description = "Annotate AST trees with source code positions" description = "Annotate AST trees with source code positions"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"}, {file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"}, {file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
@@ -32,6 +34,7 @@ version = "0.9.1"
description = "A python CAD programming library" description = "A python CAD programming library"
optional = false optional = false
python-versions = "<3.14,>=3.10" python-versions = "<3.14,>=3.10"
groups = ["main"]
files = [ files = [
{file = "build123d-0.9.1-py3-none-any.whl", hash = "sha256:0e2a3c171d7db55329201438a95ab888df6c1b0fd13067a3780efcc8fbe1cc78"}, {file = "build123d-0.9.1-py3-none-any.whl", hash = "sha256:0e2a3c171d7db55329201438a95ab888df6c1b0fd13067a3780efcc8fbe1cc78"},
{file = "build123d-0.9.1.tar.gz", hash = "sha256:8bc179cb65c7e7393191ad7113f13781fabf75892be07060f2bebf95369c093b"}, {file = "build123d-0.9.1.tar.gz", hash = "sha256:8bc179cb65c7e7393191ad7113f13781fabf75892be07060f2bebf95369c093b"},
@@ -63,6 +66,7 @@ version = "7.8.1.1.post1"
description = "Python wrapper for Open CASCADE Technology 3D geometry library based on the official CadQuery/OCP sources" description = "Python wrapper for Open CASCADE Technology 3D geometry library based on the official CadQuery/OCP sources"
optional = false optional = false
python-versions = "<3.14,>=3.10" python-versions = "<3.14,>=3.10"
groups = ["main"]
files = [ files = [
{file = "cadquery_ocp-7.8.1.1.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4413961e98a90686a56c2ac58b126773c7da4eb82b967ddcc1f394fe6a7b71ad"}, {file = "cadquery_ocp-7.8.1.1.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4413961e98a90686a56c2ac58b126773c7da4eb82b967ddcc1f394fe6a7b71ad"},
{file = "cadquery_ocp-7.8.1.1.post1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:687c8a22d6248b28e44f136cecf93d274a897efbd0d8b36e38d4e207fecf1d84"}, {file = "cadquery_ocp-7.8.1.1.post1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:687c8a22d6248b28e44f136cecf93d274a897efbd0d8b36e38d4e207fecf1d84"},
@@ -91,10 +95,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text." description = "Cross-platform colored terminal text."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
groups = ["main", "dev"]
files = [ files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
] ]
markers = {main = "sys_platform == \"win32\""}
[[package]] [[package]]
name = "contourpy" name = "contourpy"
@@ -102,6 +108,7 @@ version = "1.3.2"
description = "Python library for calculating contours of 2D quadrilateral grids" description = "Python library for calculating contours of 2D quadrilateral grids"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ba38e3f9f330af820c4b27ceb4b9c7feee5fe0493ea53a8720f4792667465934"},
{file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"}, {file = "contourpy-1.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"},
@@ -178,6 +185,7 @@ version = "0.12.1"
description = "Composable style cycles" description = "Composable style cycles"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"}, {file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
{file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"}, {file = "cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c"},
@@ -193,6 +201,7 @@ version = "0.6.7"
description = "Easily serialize dataclasses to and from JSON." description = "Easily serialize dataclasses to and from JSON."
optional = false optional = false
python-versions = "<4.0,>=3.7" python-versions = "<4.0,>=3.7"
groups = ["main"]
files = [ files = [
{file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"}, {file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"},
{file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"}, {file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"},
@@ -208,6 +217,7 @@ version = "5.2.1"
description = "Decorators for Humans" description = "Decorators for Humans"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
{file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"},
@@ -219,6 +229,7 @@ version = "1.2.18"
description = "Python @deprecated decorator to deprecate old python classes, functions or methods." description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
groups = ["main"]
files = [ files = [
{file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"},
{file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"},
@@ -228,7 +239,7 @@ files = [
wrapt = ">=1.10,<2" wrapt = ">=1.10,<2"
[package.extras] [package.extras]
dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"]
[[package]] [[package]]
name = "exceptiongroup" name = "exceptiongroup"
@@ -236,6 +247,8 @@ version = "1.3.0"
description = "Backport of PEP 654 (exception groups)" description = "Backport of PEP 654 (exception groups)"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
groups = ["main"]
markers = "python_version == \"3.10\""
files = [ files = [
{file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
{file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"},
@@ -253,13 +266,14 @@ version = "2.2.0"
description = "Get the currently executing AST node of a frame, and other information" description = "Get the currently executing AST node of a frame, and other information"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"},
{file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"},
] ]
[package.extras] [package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""]
[[package]] [[package]]
name = "ezdxf" name = "ezdxf"
@@ -267,6 +281,7 @@ version = "1.4.2"
description = "A Python package to create/manipulate DXF drawings." description = "A Python package to create/manipulate DXF drawings."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "ezdxf-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:94e62d4da459d86e6d1630c09109b7b6fd9672f7ff8fab4eb9d3c91f75e0a18e"}, {file = "ezdxf-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:94e62d4da459d86e6d1630c09109b7b6fd9672f7ff8fab4eb9d3c91f75e0a18e"},
{file = "ezdxf-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:63aae1eb68aa30e1bbee38886e5b8bf602c9a4a0b672f0b2b31c5cdb29d6c533"}, {file = "ezdxf-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:63aae1eb68aa30e1bbee38886e5b8bf602c9a4a0b672f0b2b31c5cdb29d6c533"},
@@ -330,6 +345,7 @@ version = "4.58.5"
description = "Tools to manipulate font files" description = "Tools to manipulate font files"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "fonttools-4.58.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d500d399aa4e92d969a0d21052696fa762385bb23c3e733703af4a195ad9f34c"}, {file = "fonttools-4.58.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d500d399aa4e92d969a0d21052696fa762385bb23c3e733703af4a195ad9f34c"},
{file = "fonttools-4.58.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b00530b84f87792891874938bd42f47af2f7f4c2a1d70466e6eb7166577853ab"}, {file = "fonttools-4.58.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b00530b84f87792891874938bd42f47af2f7f4c2a1d70466e6eb7166577853ab"},
@@ -376,18 +392,18 @@ files = [
] ]
[package.extras] [package.extras]
all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] all = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\"", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0) ; python_version <= \"3.12\"", "xattr ; sys_platform == \"darwin\"", "zopfli (>=0.1.4)"]
graphite = ["lz4 (>=1.7.4.2)"] graphite = ["lz4 (>=1.7.4.2)"]
interpolatable = ["munkres", "pycairo", "scipy"] interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""]
lxml = ["lxml (>=4.0)"] lxml = ["lxml (>=4.0)"]
pathops = ["skia-pathops (>=0.5.0)"] pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"] plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"] repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"] symfont = ["sympy"]
type1 = ["xattr"] type1 = ["xattr ; sys_platform == \"darwin\""]
ufo = ["fs (>=2.2.0,<3)"] ufo = ["fs (>=2.2.0,<3)"]
unicode = ["unicodedata2 (>=15.1.0)"] unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""]
woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"]
[[package]] [[package]]
name = "ipython" name = "ipython"
@@ -395,6 +411,7 @@ version = "8.37.0"
description = "IPython: Productive Interactive Computing" description = "IPython: Productive Interactive Computing"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2"}, {file = "ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2"},
{file = "ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216"}, {file = "ipython-8.37.0.tar.gz", hash = "sha256:ca815841e1a41a1e6b73a0b08f3038af9b2252564d01fc405356d34033012216"},
@@ -416,7 +433,7 @@ typing_extensions = {version = ">=4.6", markers = "python_version < \"3.12\""}
[package.extras] [package.extras]
all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
black = ["black"] black = ["black"]
doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing_extensions"] doc = ["docrepr", "exceptiongroup", "intersphinx_registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli ; python_version < \"3.11\"", "typing_extensions"]
kernel = ["ipykernel"] kernel = ["ipykernel"]
matplotlib = ["matplotlib"] matplotlib = ["matplotlib"]
nbconvert = ["nbconvert"] nbconvert = ["nbconvert"]
@@ -433,6 +450,7 @@ version = "0.19.2"
description = "An autocompletion tool for Python that can be used for text editors." description = "An autocompletion tool for Python that can be used for text editors."
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
{file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"},
@@ -452,6 +470,7 @@ version = "1.4.8"
description = "A fast implementation of the Cassowary constraint solver" description = "A fast implementation of the Cassowary constraint solver"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"}, {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88c6f252f6816a73b1f8c904f7bbe02fd67c09a69f7cb8a0eecdbf5ce78e63db"},
{file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"}, {file = "kiwisolver-1.4.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c72941acb7b67138f35b879bbe85be0f6c6a70cab78fe3ef6db9c024d9223e5b"},
@@ -541,6 +560,7 @@ version = "3.26.1"
description = "A lightweight library for converting complex datatypes to and from native Python datatypes." description = "A lightweight library for converting complex datatypes to and from native Python datatypes."
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"}, {file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"},
{file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"}, {file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"},
@@ -560,6 +580,7 @@ version = "3.10.3"
description = "Python plotting package" description = "Python plotting package"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7"}, {file = "matplotlib-3.10.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:213fadd6348d106ca7db99e113f1bea1e65e383c3ba76e8556ba4a3054b65ae7"},
{file = "matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb"}, {file = "matplotlib-3.10.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb"},
@@ -617,6 +638,7 @@ version = "0.1.7"
description = "Inline Matplotlib backend for Jupyter" description = "Inline Matplotlib backend for Jupyter"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
@@ -625,12 +647,26 @@ files = [
[package.dependencies] [package.dependencies]
traitlets = "*" traitlets = "*"
[[package]]
name = "mslex"
version = "1.3.0"
description = "shlex for windows"
optional = false
python-versions = ">=3.5"
groups = ["dev"]
markers = "sys_platform == \"win32\""
files = [
{file = "mslex-1.3.0-py3-none-any.whl", hash = "sha256:c7074b347201b3466fc077c5692fbce9b5f62a63a51f537a53fbbd02eff2eea4"},
{file = "mslex-1.3.0.tar.gz", hash = "sha256:641c887d1d3db610eee2af37a8e5abda3f70b3006cdfd2d0d29dc0d1ae28a85d"},
]
[[package]] [[package]]
name = "mypy-extensions" name = "mypy-extensions"
version = "1.1.0" version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker." description = "Type system extensions for programs checked with the mypy type checker."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"},
@@ -642,6 +678,7 @@ version = "2.2.6"
description = "Fundamental package for array computing in Python" description = "Fundamental package for array computing in Python"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"}, {file = "numpy-2.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b412caa66f72040e6d268491a59f2c43bf03eb6c96dd8f0307829feb7fa2b6fb"},
{file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"}, {file = "numpy-2.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"},
@@ -706,6 +743,7 @@ version = "0.5.0"
description = "" description = ""
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "ocpsvg-0.5.0-py3-none-any.whl", hash = "sha256:68cafdc3d681a1707530360baf2d51cfd58414b7d439f42eafbd31e842cf295e"}, {file = "ocpsvg-0.5.0-py3-none-any.whl", hash = "sha256:68cafdc3d681a1707530360baf2d51cfd58414b7d439f42eafbd31e842cf295e"},
{file = "ocpsvg-0.5.0.tar.gz", hash = "sha256:5cd8dbec8bf590d373a82aaebeab241838185aab04ee2859f33b9d7956bbfba6"}, {file = "ocpsvg-0.5.0.tar.gz", hash = "sha256:5cd8dbec8bf590d373a82aaebeab241838185aab04ee2859f33b9d7956bbfba6"},
@@ -724,6 +762,7 @@ version = "25.0"
description = "Core utilities for Python packages" description = "Core utilities for Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
@@ -735,6 +774,7 @@ version = "0.8.4"
description = "A Python Parser" description = "A Python Parser"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"},
{file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"},
@@ -750,6 +790,8 @@ version = "4.9.0"
description = "Pexpect allows easy control of interactive console applications." description = "Pexpect allows easy control of interactive console applications."
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
files = [ files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
@@ -764,6 +806,7 @@ version = "11.3.0"
description = "Python Imaging Library (Fork)" description = "Python Imaging Library (Fork)"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"}, {file = "pillow-11.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b9c17fd4ace828b3003dfd1e30bff24863e0eb59b535e8f80194d9cc7ecf860"},
{file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"}, {file = "pillow-11.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"},
@@ -879,15 +922,28 @@ fpx = ["olefile"]
mic = ["olefile"] mic = ["olefile"]
test-arrow = ["pyarrow"] test-arrow = ["pyarrow"]
tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"] tests = ["check-manifest", "coverage (>=7.4.2)", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "trove-classifiers (>=2024.10.12)"]
typing = ["typing-extensions"] typing = ["typing-extensions ; python_version < \"3.10\""]
xmp = ["defusedxml"] xmp = ["defusedxml"]
[[package]]
name = "poetry-core"
version = "2.1.3"
description = "Poetry PEP 517 Build Backend"
optional = false
python-versions = "<4.0,>=3.9"
groups = ["main"]
files = [
{file = "poetry_core-2.1.3-py3-none-any.whl", hash = "sha256:2c704f05016698a54ca1d327f46ce2426d72eaca6ff614132c8477c292266771"},
{file = "poetry_core-2.1.3.tar.gz", hash = "sha256:0522a015477ed622c89aad56a477a57813cace0c8e7ff2a2906b7ef4a2e296a4"},
]
[[package]] [[package]]
name = "prompt-toolkit" name = "prompt-toolkit"
version = "3.0.51" version = "3.0.51"
description = "Library for building powerful interactive command lines in Python" description = "Library for building powerful interactive command lines in Python"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"}, {file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"},
{file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"}, {file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"},
@@ -896,12 +952,45 @@ files = [
[package.dependencies] [package.dependencies]
wcwidth = "*" wcwidth = "*"
[[package]]
name = "psutil"
version = "6.1.1"
description = "Cross-platform lib for process and system monitoring in Python."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
groups = ["dev"]
files = [
{file = "psutil-6.1.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:9ccc4316f24409159897799b83004cb1e24f9819b0dcf9c0b68bdcb6cefee6a8"},
{file = "psutil-6.1.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ca9609c77ea3b8481ab005da74ed894035936223422dc591d6772b147421f777"},
{file = "psutil-6.1.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:8df0178ba8a9e5bc84fed9cfa61d54601b371fbec5c8eebad27575f1e105c0d4"},
{file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:1924e659d6c19c647e763e78670a05dbb7feaf44a0e9c94bf9e14dfc6ba50468"},
{file = "psutil-6.1.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:018aeae2af92d943fdf1da6b58665124897cfc94faa2ca92098838f83e1b1bca"},
{file = "psutil-6.1.1-cp27-none-win32.whl", hash = "sha256:6d4281f5bbca041e2292be3380ec56a9413b790579b8e593b1784499d0005dac"},
{file = "psutil-6.1.1-cp27-none-win_amd64.whl", hash = "sha256:c777eb75bb33c47377c9af68f30e9f11bc78e0f07fbf907be4a5d70b2fe5f030"},
{file = "psutil-6.1.1-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:fc0ed7fe2231a444fc219b9c42d0376e0a9a1a72f16c5cfa0f68d19f1a0663e8"},
{file = "psutil-6.1.1-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0bdd4eab935276290ad3cb718e9809412895ca6b5b334f5a9111ee6d9aff9377"},
{file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b6e06c20c05fe95a3d7302d74e7097756d4ba1247975ad6905441ae1b5b66003"},
{file = "psutil-6.1.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97f7cb9921fbec4904f522d972f0c0e1f4fabbdd4e0287813b21215074a0f160"},
{file = "psutil-6.1.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33431e84fee02bc84ea36d9e2c4a6d395d479c9dd9bba2376c1f6ee8f3a4e0b3"},
{file = "psutil-6.1.1-cp36-cp36m-win32.whl", hash = "sha256:384636b1a64b47814437d1173be1427a7c83681b17a450bfc309a1953e329603"},
{file = "psutil-6.1.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8be07491f6ebe1a693f17d4f11e69d0dc1811fa082736500f649f79df7735303"},
{file = "psutil-6.1.1-cp37-abi3-win32.whl", hash = "sha256:eaa912e0b11848c4d9279a93d7e2783df352b082f40111e078388701fd479e53"},
{file = "psutil-6.1.1-cp37-abi3-win_amd64.whl", hash = "sha256:f35cfccb065fff93529d2afb4a2e89e363fe63ca1e4a5da22b603a85833c2649"},
{file = "psutil-6.1.1.tar.gz", hash = "sha256:cf8496728c18f2d0b45198f06895be52f36611711746b7f30c464b422b50e2f5"},
]
[package.extras]
dev = ["abi3audit", "black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "vulture", "wheel"]
test = ["pytest", "pytest-xdist", "setuptools"]
[[package]] [[package]]
name = "ptyprocess" name = "ptyprocess"
version = "0.7.0" version = "0.7.0"
description = "Run a subprocess in a pseudo terminal" description = "Run a subprocess in a pseudo terminal"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
files = [ files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
@@ -913,6 +1002,7 @@ version = "0.2.3"
description = "Safely evaluate AST nodes without side effects" description = "Safely evaluate AST nodes without side effects"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
{file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
@@ -927,6 +1017,7 @@ version = "2.3.1"
description = "A python package for Lib3MF tools" description = "A python package for Lib3MF tools"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "py_lib3mf-2.3.1-py3-none-any.whl", hash = "sha256:86a870ef386debba9b74683d3a08125a34c153aaa65e967f61677cc5a0a65e24"}, {file = "py_lib3mf-2.3.1-py3-none-any.whl", hash = "sha256:86a870ef386debba9b74683d3a08125a34c153aaa65e967f61677cc5a0a65e24"},
] ]
@@ -937,6 +1028,7 @@ version = "1.16.4"
description = "Python library for reading, writing and managing 3D objects in the Khronos Group gltf and gltf2 formats." description = "Python library for reading, writing and managing 3D objects in the Khronos Group gltf and gltf2 formats."
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "pygltflib-1.16.4-py3-none-any.whl", hash = "sha256:1cb5886ca12ac512bbea3b5640f1e15575844418b1f1574de42ec19175e00f53"}, {file = "pygltflib-1.16.4-py3-none-any.whl", hash = "sha256:1cb5886ca12ac512bbea3b5640f1e15575844418b1f1574de42ec19175e00f53"},
{file = "pygltflib-1.16.4.tar.gz", hash = "sha256:7816253ef51b07a208864b94332bce8c509aa0f9b827d8771cf271293ecebb11"}, {file = "pygltflib-1.16.4.tar.gz", hash = "sha256:7816253ef51b07a208864b94332bce8c509aa0f9b827d8771cf271293ecebb11"},
@@ -952,6 +1044,7 @@ version = "2.19.2"
description = "Pygments is a syntax highlighting package written in Python." description = "Pygments is a syntax highlighting package written in Python."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"},
@@ -966,6 +1059,7 @@ version = "3.2.3"
description = "pyparsing module - Classes and methods to define and execute parsing grammars" description = "pyparsing module - Classes and methods to define and execute parsing grammars"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"}, {file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"},
{file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"}, {file = "pyparsing-3.2.3.tar.gz", hash = "sha256:b9c13f1ab8b3b542f72e28f634bad4de758ab3ce4546e4301970ad6fa77c38be"},
@@ -980,6 +1074,7 @@ version = "2.9.0.post0"
description = "Extensions to the standard Python datetime module" description = "Extensions to the standard Python datetime module"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["main"]
files = [ files = [
{file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"},
{file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"},
@@ -994,6 +1089,7 @@ version = "1.15.3"
description = "Fundamental algorithms for scientific computing in Python" description = "Fundamental algorithms for scientific computing in Python"
optional = false optional = false
python-versions = ">=3.10" python-versions = ">=3.10"
groups = ["main"]
files = [ files = [
{file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"}, {file = "scipy-1.15.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:a345928c86d535060c9c2b25e71e87c39ab2f22fc96e9636bd74d1dbf9de448c"},
{file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"}, {file = "scipy-1.15.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"},
@@ -1049,7 +1145,7 @@ numpy = ">=1.23.5,<2.5"
[package.extras] [package.extras]
dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"] dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"] doc = ["intersphinx_registry", "jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.19.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<8.0.0)", "sphinx-copybutton", "sphinx-design (>=0.4.0)"]
test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] test = ["Cython", "array-api-strict (>=2.0,<2.1.1)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja ; sys_platform != \"emscripten\"", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
[[package]] [[package]]
name = "six" name = "six"
@@ -1057,6 +1153,7 @@ version = "1.17.0"
description = "Python 2 and 3 compatibility utilities" description = "Python 2 and 3 compatibility utilities"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
groups = ["main"]
files = [ files = [
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
@@ -1068,6 +1165,7 @@ version = "0.6.3"
description = "Extract data from python stack frames and tracebacks for informative displays" description = "Extract data from python stack frames and tracebacks for informative displays"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
@@ -1087,6 +1185,7 @@ version = "1.9.6"
description = "Svg Elements Parsing" description = "Svg Elements Parsing"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "svgelements-1.9.6-py2.py3-none-any.whl", hash = "sha256:8a5cf2cc066d98e713d5b875b1d6e5eeb9b92e855e835ebd7caab2713ae1dcad"}, {file = "svgelements-1.9.6-py2.py3-none-any.whl", hash = "sha256:8a5cf2cc066d98e713d5b875b1d6e5eeb9b92e855e835ebd7caab2713ae1dcad"},
{file = "svgelements-1.9.6.tar.gz", hash = "sha256:7c02ad6404cd3d1771fd50e40fbfc0550b0893933466f86a6eb815f3ba3f37f7"}, {file = "svgelements-1.9.6.tar.gz", hash = "sha256:7c02ad6404cd3d1771fd50e40fbfc0550b0893933466f86a6eb815f3ba3f37f7"},
@@ -1098,6 +1197,7 @@ version = "1.7.1"
description = "A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves." description = "A collection of tools for manipulating and analyzing SVG Path objects and Bezier curves."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "svgpathtools-1.7.1-py2.py3-none-any.whl", hash = "sha256:3cbb8ba0e8d200f9639034608d9c55b68efbc1bef99ea99559a3e7cb024fb738"}, {file = "svgpathtools-1.7.1-py2.py3-none-any.whl", hash = "sha256:3cbb8ba0e8d200f9639034608d9c55b68efbc1bef99ea99559a3e7cb024fb738"},
{file = "svgpathtools-1.7.1.tar.gz", hash = "sha256:beaef20fd78164aa5f0a7d4fd164ef20cb0d3d015cdec50c8c168e9d6547f041"}, {file = "svgpathtools-1.7.1.tar.gz", hash = "sha256:beaef20fd78164aa5f0a7d4fd164ef20cb0d3d015cdec50c8c168e9d6547f041"},
@@ -1114,17 +1214,79 @@ version = "1.4.3"
description = "A Python library to create SVG drawings." description = "A Python library to create SVG drawings."
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
groups = ["main"]
files = [ files = [
{file = "svgwrite-1.4.3-py3-none-any.whl", hash = "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d"}, {file = "svgwrite-1.4.3-py3-none-any.whl", hash = "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d"},
{file = "svgwrite-1.4.3.zip", hash = "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3"}, {file = "svgwrite-1.4.3.zip", hash = "sha256:a8fbdfd4443302a6619a7f76bc937fc683daf2628d9b737c891ec08b8ce524c3"},
] ]
[[package]]
name = "taskipy"
version = "1.14.1"
description = "tasks runner for python projects"
optional = false
python-versions = "<4.0,>=3.6"
groups = ["dev"]
files = [
{file = "taskipy-1.14.1-py3-none-any.whl", hash = "sha256:6e361520f29a0fd2159848e953599f9c75b1d0b047461e4965069caeb94908f1"},
{file = "taskipy-1.14.1.tar.gz", hash = "sha256:410fbcf89692dfd4b9f39c2b49e1750b0a7b81affd0e2d7ea8c35f9d6a4774ed"},
]
[package.dependencies]
colorama = ">=0.4.4,<0.5.0"
mslex = {version = ">=1.1.0,<2.0.0", markers = "sys_platform == \"win32\""}
psutil = ">=5.7.2,<7"
tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version >= \"3.7\" and python_version < \"4.0\""}
[[package]]
name = "tomli"
version = "2.2.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.8"
groups = ["dev"]
files = [
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
]
[[package]] [[package]]
name = "traitlets" name = "traitlets"
version = "5.14.3" version = "5.14.3"
description = "Traitlets Python configuration system" description = "Traitlets Python configuration system"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
@@ -1140,6 +1302,7 @@ version = "1.2"
description = "Find all the sides and angles of a triangle, if you know some of the sides and/or angles. (Uses the Law of Sines and Law of Cosines.)" description = "Find all the sides and angles of a triangle, if you know some of the sides and/or angles. (Uses the Law of Sines and Law of Cosines.)"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "trianglesolver-1.2-py3-none-any.whl", hash = "sha256:aa0903c3708b4e2b496f06d490cae72c6ff6274b00d1edce420fcfa3b2b76682"}, {file = "trianglesolver-1.2-py3-none-any.whl", hash = "sha256:aa0903c3708b4e2b496f06d490cae72c6ff6274b00d1edce420fcfa3b2b76682"},
{file = "trianglesolver-1.2.tar.gz", hash = "sha256:4af18aade579d5c0d64389b3e65aeaf06cff26319762ccd859e3268559a76aea"}, {file = "trianglesolver-1.2.tar.gz", hash = "sha256:4af18aade579d5c0d64389b3e65aeaf06cff26319762ccd859e3268559a76aea"},
@@ -1151,6 +1314,7 @@ version = "4.14.1"
description = "Backported and Experimental Type Hints for Python 3.9+" description = "Backported and Experimental Type Hints for Python 3.9+"
optional = false optional = false
python-versions = ">=3.9" python-versions = ">=3.9"
groups = ["main"]
files = [ files = [
{file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"}, {file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"},
{file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"}, {file = "typing_extensions-4.14.1.tar.gz", hash = "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36"},
@@ -1162,6 +1326,7 @@ version = "0.9.0"
description = "Runtime inspection utilities for typing module." description = "Runtime inspection utilities for typing module."
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"}, {file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
{file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"}, {file = "typing_inspect-0.9.0.tar.gz", hash = "sha256:b23fc42ff6f6ef6954e4852c1fb512cdd18dbea03134f91f856a95ccc9461f78"},
@@ -1177,6 +1342,7 @@ version = "9.3.1"
description = "VTK is an open-source toolkit for 3D computer graphics, image processing, and visualization" description = "VTK is an open-source toolkit for 3D computer graphics, image processing, and visualization"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "vtk-9.3.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:c41ed344b9cc90ee9dcfc5967815de272985647d0c8e0a57f0e8b4229bc1b0b9"}, {file = "vtk-9.3.1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:c41ed344b9cc90ee9dcfc5967815de272985647d0c8e0a57f0e8b4229bc1b0b9"},
{file = "vtk-9.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84c04327becc4c4dfe1fb04248baa4b5c480f188a9d52f4b912b163d33622442"}, {file = "vtk-9.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84c04327becc4c4dfe1fb04248baa4b5c480f188a9d52f4b912b163d33622442"},
@@ -1218,6 +1384,7 @@ version = "0.2.13"
description = "Measures the displayed width of unicode strings in a terminal" description = "Measures the displayed width of unicode strings in a terminal"
optional = false optional = false
python-versions = "*" python-versions = "*"
groups = ["main"]
files = [ files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
@@ -1229,6 +1396,7 @@ version = "1.17.2"
description = "Module for decorators, wrappers and monkey patching." description = "Module for decorators, wrappers and monkey patching."
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
groups = ["main"]
files = [ files = [
{file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"},
{file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"},
@@ -1312,6 +1480,6 @@ files = [
] ]
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.1"
python-versions = ">=3.10,<3.13" python-versions = ">=3.10,<3.13"
content-hash = "93d841ad150ea31f388ae73a764e7a0491e65ace01fa1bad7b66dbf6cba4b459" content-hash = "17564ce50af9a75e84337c83f77d86c9d2ff73a366de70ba27453e529bb468e9"

View File

@@ -1,15 +1,24 @@
[build-system]
requires = ["poetry-core==2.1.3", "taskipy==1.14.1"]
build-backend = "poetry.core.masonry.api"
[tool.poetry] [tool.poetry]
name = "yacv-server" name = "yacv-server"
version = "0.9.7" version = "0.10.0-alpha.4"
description = "Yet Another CAD Viewer (server)" description = "Yet Another CAD Viewer (server)"
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"] authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
license = "MIT" license = "MIT"
readme = "README.md" readme = "README.md"
include = [ include = [
{ path = 'yacv_server/frontend/*', format = 'wheel' }, { path = 'yacv_server/frontend/**/*', format = 'wheel' },
{ path = 'yacv_server/frontend/*', format = 'sdist' }, { path = 'yacv_server/frontend/**/*', format = 'sdist' },
] ]
[tool.taskipy.tasks]
build = "task build_frontend && task build_backend"
build_frontend = "rm -rf yacv_server/frontend || true && yarn install && YACV_SMALL_BUILD=true yarn build --outDir yacv_server/frontend"
build_backend = "poetry build --format wheel"
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = ">=3.10,<3.13" # Due to vtk transitive dependency of build123d -> cadquery-ocp -> vtk python = ">=3.10,<3.13" # Due to vtk transitive dependency of build123d -> cadquery-ocp -> vtk
@@ -19,11 +28,7 @@ build123d = ">=0.9,<0.10"
# Misc # Misc
pygltflib = "^1.16.2" pygltflib = "^1.16.2"
pillow = ">=10.2,<12.0" pillow = ">=10.2,<12.0"
poetry-core = "==2.1.3"
[tool.poetry.build] [tool.poetry.group.dev.dependencies]
generate-setup-file = false taskipy = "^1.14.1"
script = "build.py"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

View File

@@ -1,12 +1,17 @@
import {fileURLToPath, URL} from 'node:url' import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite' import {defineConfig} from 'vite'
// @ts-ignore
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
// @ts-ignore
import vueJsx from '@vitejs/plugin-vue-jsx' import vueJsx from '@vitejs/plugin-vue-jsx'
import {name, version} from './package.json' import {name, version} from './package.json'
import {execSync} from 'child_process' import {execSync} from 'child_process'
import {viteStaticCopy} from "vite-plugin-static-copy"; import {viteStaticCopy} from "vite-plugin-static-copy";
import {dirname, join} from "path"; import {dirname, join} from "path";
import {version as pyodideVersion} from "pyodide";
let wantsSmallBuild = process.env.YACV_SMALL_BUILD == "true";
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
@@ -15,7 +20,7 @@ export default defineConfig({
vue({ vue({
template: { template: {
compilerOptions: { compilerOptions: {
isCustomElement: tag => tag == 'model-viewer' isCustomElement: (tag: string) => tag == 'model-viewer'
} }
} }
}), }),
@@ -37,8 +42,17 @@ export default defineConfig({
rollupOptions: { rollupOptions: {
output: { output: {
experimentalMinChunkSize: 512000, // 512KB (avoid too many small chunks) experimentalMinChunkSize: 512000, // 512KB (avoid too many small chunks)
} },
} external: wantsSmallBuild ? [
// Exclude some large optional dependencies if small build is requested (for embedding in python package)
"pyodide",
/.*\/pyodide-worker.*/,
"monaco-editor",
/monaco-editor\/.*/,
"@guolao/vue-monaco-editor",
/three\/examples\/jsm\/libs\/draco\/draco_(en|de)coder\.js/,
] : [],
},
}, },
worker: { worker: {
format: 'es', // Use ES modules for workers (IIFE is not supported with code-splitting) format: 'es', // Use ES modules for workers (IIFE is not supported with code-splitting)
@@ -48,6 +62,7 @@ export default defineConfig({
__APP_VERSION__: JSON.stringify(version), __APP_VERSION__: JSON.stringify(version),
__APP_GIT_SHA__: JSON.stringify(execSync('git rev-parse HEAD').toString().trim()), __APP_GIT_SHA__: JSON.stringify(execSync('git rev-parse HEAD').toString().trim()),
__APP_GIT_DIRTY__: JSON.stringify(execSync('git diff --quiet || echo dirty').toString().trim()), __APP_GIT_DIRTY__: JSON.stringify(execSync('git diff --quiet || echo dirty').toString().trim()),
__YACV_SMALL_BUILD__: JSON.stringify(wantsSmallBuild),
} }
}) })
@@ -59,12 +74,13 @@ function viteStaticCopyPyodide() {
"!**/*.whl", "!**/*.whl",
"!**/node_modules", "!**/node_modules",
]; ];
// @ts-ignore
const pyodideDir = dirname(fileURLToPath(import.meta.resolve("pyodide"))); const pyodideDir = dirname(fileURLToPath(import.meta.resolve("pyodide")));
return viteStaticCopy({ return viteStaticCopy({
targets: [ targets: wantsSmallBuild ? [] : [
{ {
src: [join(pyodideDir, "*")].concat(PYODIDE_EXCLUDE), src: [join(pyodideDir, "*")].concat(PYODIDE_EXCLUDE),
dest: "assets", dest: "pyodide-v" + pyodideVersion, // It would be better to use hashed names instead of folder...
}, },
], ],
}); });

View File

@@ -54,8 +54,8 @@ class BufferedPubSub(Generic[T]):
self._subscribers.remove(q) self._subscribers.remove(q)
logger.debug(f"Unsubscribed from %s (%d subscribers)", self, len(self._subscribers)) logger.debug(f"Unsubscribed from %s (%d subscribers)", self, len(self._subscribers))
def subscribe(self, include_buffered: bool = True, include_future: bool = True, yield_timeout: float = 0.0) -> \ def subscribe(self, include_buffered: bool = True, include_future: bool = True,
Generator[T, None, None]: yield_timeout: float | None = 0.0) -> Generator[T, None, None]:
"""Subscribes to events as a generator that yields events and automatically unsubscribes""" """Subscribes to events as a generator that yields events and automatically unsubscribes"""
q = self._subscribe(include_buffered, include_future) q = self._subscribe(include_buffered, include_future)
try: try:

View File

@@ -3,8 +3,8 @@ from typing import List, Dict, Tuple, Optional
from OCP.BRep import BRep_Tool from OCP.BRep import BRep_Tool
from OCP.BRepAdaptor import BRepAdaptor_Curve from OCP.BRepAdaptor import BRepAdaptor_Curve
from OCP.GCPnts import GCPnts_TangentialDeflection from OCP.GCPnts import GCPnts_TangentialDeflection
from OCP.OCP.BRepLib import BRepLib_ToolTriangulatedShape from OCP.BRepLib import BRepLib_ToolTriangulatedShape
from OCP.OCP.TopAbs import TopAbs_Orientation from OCP.TopAbs import TopAbs_Orientation
from OCP.TopLoc import TopLoc_Location from OCP.TopLoc import TopLoc_Location
from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
from build123d import Vertex, Face, Location, Compound, Vector from build123d import Vertex, Face, Location, Compound, Vector

View File

@@ -8,6 +8,7 @@ import sys
import threading import threading
import time import time
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum, auto
from http.server import ThreadingHTTPServer from http.server import ThreadingHTTPServer
from io import BytesIO from io import BytesIO
from threading import Thread from threading import Thread
@@ -64,10 +65,20 @@ class UpdatesApiFullData(UpdatesApiData):
return super().to_json() return super().to_json()
class YACVProtocol(Enum):
"""Enum of communication protocols supported by the server"""
HTTP = auto()
"""The recommended protocol for any platform that can run a web server."""
STDERR = auto()
"""Prints the updates one by one to stderr (first metadata, then base64 of glb file) using a special prefix. Required for Pyodide support."""
class YACV: class YACV:
"""The main yacv_server class, which manages the web server and the CAD objects.""" """The main yacv_server class, which manages the web server and the CAD objects."""
# Startup # Startup
protocol: YACVProtocol
"""The protocol used by the server. Defaults to HTTP, but can be set to STDERR for Pyodide support."""
server_thread: Optional[Thread] server_thread: Optional[Thread]
"""The main thread running the server (will spawn other threads for each request)""" """The main thread running the server (will spawn other threads for each request)"""
server: Optional[ThreadingHTTPServer] server: Optional[ThreadingHTTPServer]
@@ -127,6 +138,10 @@ class YACV:
in the hexadecimal format #RRGGBB or #RRGGBBAA.""" in the hexadecimal format #RRGGBB or #RRGGBBAA."""
def __init__(self): def __init__(self):
"""Initializes the YACV server"""
raw_protocol = os.getenv('YACV_PROTOCOL', 'http' if sys.platform != 'emscripten' else 'stderr').upper()
self.protocol = YACVProtocol[raw_protocol] if raw_protocol in YACVProtocol.__members__ else YACVProtocol.HTTP
self.protocol = YACVProtocol.STDERR
self.server_thread = None self.server_thread = None
self.server = None self.server = None
self.startup_complete = threading.Event() self.startup_complete = threading.Event()
@@ -144,6 +159,7 @@ class YACV:
def start(self): def start(self):
"""Starts the web server in the background""" """Starts the web server in the background"""
if self.protocol == YACVProtocol.STDERR: return # No server to start, just print to stderr
assert self.server_thread is None, "Server currently running, cannot start another one" assert self.server_thread is None, "Server currently running, cannot start another one"
assert self.startup_complete.is_set() is False, "Server already started" assert self.startup_complete.is_set() is False, "Server already started"
# Start the server in a separate daemon thread # Start the server in a separate daemon thread
@@ -160,6 +176,8 @@ class YACV:
# noinspection PyUnusedLocal # noinspection PyUnusedLocal
def stop(self, *args): def stop(self, *args):
"""Stops the web server""" """Stops the web server"""
if self.protocol == YACVProtocol.STDERR: return # No server to stop, just print to stderr
# The remainder is for the HTTP protocol only
if self.server_thread is None: if self.server_thread is None:
logger.error('Cannot stop server because it is not running') logger.error('Cannot stop server because it is not running')
return return
@@ -167,7 +185,7 @@ class YACV:
# Inform the server that we are shutting down # Inform the server that we are shutting down
self.shutting_down.set() self.shutting_down.set()
# noinspection PyTypeChecker # noinspection PyTypeChecker
self.show_events.publish(UpdatesApiFullData(name='__shutdown', _hash='', is_remove=None, obj=None)) self._show_event(UpdatesApiFullData(name='__shutdown', _hash='', is_remove=None, obj=None))
# If we were too fast, ensure that at least one client has connected # If we were too fast, ensure that at least one client has connected
graceful_secs_connect = float(os.getenv('YACV_GRACEFUL_SECS_CONNECT', 12.0)) graceful_secs_connect = float(os.getenv('YACV_GRACEFUL_SECS_CONNECT', 12.0))
@@ -195,9 +213,11 @@ class YACV:
if len(args) >= 1 and args[0] in (signal.SIGINT, signal.SIGTERM): if len(args) >= 1 and args[0] in (signal.SIGINT, signal.SIGTERM):
sys.exit(0) # Exit with success sys.exit(0) # Exit with success
_yacvServerModelPrefix = "yacv_server://model/"
def _run_server(self): def _run_server(self):
"""Runs the web server""" """Runs the web server"""
logger.info('Starting server...') logger.info('Starting server in %s mode...', self.protocol.name)
self.server = ThreadingHTTPServer( self.server = ThreadingHTTPServer(
(os.getenv('YACV_HOST', 'localhost'), int(os.getenv('YACV_PORT', 32323))), (os.getenv('YACV_HOST', 'localhost'), int(os.getenv('YACV_PORT', 32323))),
lambda a, b, c: HTTPHandler(a, b, c, yacv=self)) lambda a, b, c: HTTPHandler(a, b, c, yacv=self))
@@ -206,6 +226,22 @@ class YACV:
self.startup_complete.set() self.startup_complete.set()
self.server.serve_forever() self.server.serve_forever()
def _show_event(self, event: UpdatesApiFullData):
"""Handles a show event by publishing it to the show events buffer (and special handling for stderr protocol)."""
self.show_events.publish(event)
# If the protocol is STDERR, we need to print the event to stderr
if self.protocol == YACVProtocol.STDERR:
msg = f'{self._yacvServerModelPrefix}{event.to_json()}'
if not event.is_remove:
# Always build the object even if the interface already has it (optimization disabled for Pyodide)
glb_and_hash = self.export(event.name)
if glb_and_hash is None:
logger.warning('Object %s not found, ignoring it...', event.name)
return
glb = glb_and_hash[0]
msg += f'{base64.b64encode(glb).decode("utf-8")}'
print(msg, file=sys.stderr, flush=True)
def show(self, *objs: List[YACVSupported], names: Optional[Union[str, List[str]]] = None, **kwargs): def show(self, *objs: List[YACVSupported], names: Optional[Union[str, List[str]]] = None, **kwargs):
""" """
Shows the given CAD objects in the frontend. The objects will be tessellated and converted to GLTF. Optionally, Shows the given CAD objects in the frontend. The objects will be tessellated and converted to GLTF. Optionally,
@@ -252,13 +288,13 @@ class YACV:
# Some properties may be lost in preprocessing, so save them in kwargs # Some properties may be lost in preprocessing, so save them in kwargs
_kwargs = kwargs.copy() _kwargs = kwargs.copy()
if obj_color is not None: if obj_color is not None:
_kwargs['color_obj'] = obj_color # Only applies to highest-dimensional objects _kwargs['color_obj'] = obj_color # Only applies to highest-dimensional objects
_kwargs['texture'] = _read_texture_uri(getattr(obj, 'yacv_texture', None) or kwargs.get('texture', None)) _kwargs['texture'] = _read_texture_uri(getattr(obj, 'yacv_texture', None) or kwargs.get('texture', None))
if not isinstance(obj, bytes): if not isinstance(obj, bytes):
obj = _preprocess_cad(obj, **_kwargs) obj = _preprocess_cad(obj, **_kwargs)
_hash = _hashcode(obj, **_kwargs) _hash = _hashcode(obj, **_kwargs)
event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=_kwargs or {}) event = UpdatesApiFullData(name=name, _hash=_hash, obj=obj, kwargs=_kwargs or {})
self.show_events.publish(event) self._show_event(event)
logger.info('show %s took %.3f seconds', names, time.time() - start) logger.info('show %s took %.3f seconds', names, time.time() - start)
@@ -283,7 +319,7 @@ class YACV:
# Publish the remove event # Publish the remove event
show_event = copy.copy(show_events[-1]) show_event = copy.copy(show_events[-1])
show_event.is_remove = True show_event.is_remove = True
self.show_events.publish(show_event) self._show_event(show_event)
def clear(self, except_names: List[str] = None): def clear(self, except_names: List[str] = None):
"""Clears all previously-shown objects from the scene""" """Clears all previously-shown objects from the scene"""

View File

@@ -1001,6 +1001,11 @@
dependencies: dependencies:
undici-types "~6.21.0" undici-types "~6.21.0"
"@types/pako@^2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-2.0.3.tgz#b6993334f3af27c158f3fe0dfeeba987c578afb1"
integrity sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==
"@types/stats.js@*": "@types/stats.js@*":
version "0.17.4" version "0.17.4"
resolved "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz" resolved "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz"
@@ -1944,6 +1949,11 @@ jackspeak@^4.1.1:
dependencies: dependencies:
"@isaacs/cliui" "^8.0.2" "@isaacs/cliui" "^8.0.2"
js-base64@^3.7.7:
version "3.7.7"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79"
integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==
js-tokens@^4.0.0: js-tokens@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
@@ -2431,6 +2441,11 @@ pacote@^21.0.0:
ssri "^12.0.0" ssri "^12.0.0"
tar "^6.1.11" tar "^6.1.11"
pako@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
parent-module@^1.0.0: parent-module@^1.0.0:
version "1.0.1" version "1.0.1"
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz" resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"