Console Output
diff --git a/frontend/tools/Tools.vue b/frontend/tools/Tools.vue
index 5837a1b..a76b554 100644
--- a/frontend/tools/Tools.vue
+++ b/frontend/tools/Tools.vue
@@ -21,6 +21,8 @@ import type ModelViewerWrapper from "../viewer/ModelViewerWrapper.vue";
import {defineAsyncComponent, ref, type Ref} from "vue";
import type {SelectionInfo} from "./selection";
import {settings} from "../misc/settings.ts";
+import type {NetworkUpdateEvent} from "../misc/network.ts";
+import IfNotSmallBuild from "../misc/IfNotSmallBuild.vue";
const SelectionComponent = defineAsyncComponent({
loader: () => import("./Selection.vue"),
@@ -43,7 +45,7 @@ const PlaygroundDialogContent = defineAsyncComponent({
let props = defineProps<{ viewer: InstanceType
| null }>();
-const emit = defineEmits<{ findModel: [string] }>()
+const emit = defineEmits<{ findModel: [string], updateModel: [NetworkUpdateEvent] }>()
const sett = ref(null);
const showPlaygroundDialog = ref(false);
@@ -162,14 +164,17 @@ window.addEventListener('keydown', (event) => {
Extras
-
+
Open a python editor and build models directly in the browser!
Sandbox
-
+
+ emit('updateModel', event)"/>
+
diff --git a/frontend/tools/b66.ts b/frontend/tools/b66.ts
new file mode 100644
index 0000000..9026a04
--- /dev/null
+++ b/frontend/tools/b66.ts
@@ -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);
+}
\ No newline at end of file
diff --git a/frontend/tools/pyodide-worker-api.ts b/frontend/tools/pyodide-worker-api.ts
index 5f28840..945e56a 100644
--- a/frontend/tools/pyodide-worker-api.ts
+++ b/frontend/tools/pyodide-worker-api.ts
@@ -1,44 +1,31 @@
-// Each message needs a unique id to identify the response. In a real example,
-// we might use a real uuid package
-let lastId = 1;
+import type {loadPyodide} from "pyodide";
-function getId() {
- return lastId++;
+/** Simple API for the Pyodide worker. */
+export function newPyodideWorker(initOpts: Parameters[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;
\ No newline at end of file
diff --git a/frontend/tools/pyodide-worker.ts b/frontend/tools/pyodide-worker.ts
index cee6de1..f76a6f5 100644
--- a/frontend/tools/pyodide-worker.ts
+++ b/frontend/tools/pyodide-worker.ts
@@ -1,27 +1,33 @@
-import {loadPyodide, version} from "pyodide";
+import {loadPyodide, type PyodideInterface} from "pyodide";
-let pyodideReadyPromise = loadPyodide({
- indexURL: `https://cdn.jsdelivr.net/pyodide/v${version}/full/`, // FIXME: Local deployment?
- packages: ["micropip", "sqlite3"], // Preloaded faster here...
- stdout: (msg) => self.postMessage({stdout: msg}),
- stderr: (msg) => self.postMessage({stderr: msg}),
+let myLoadPyodide = (initOpts: Parameters[0]) => loadPyodide({
+ ...initOpts,
+ stdout: (msg) => self.postMessage({stdout: msg + "\n"}), // Add newline for better readability
+ stderr: (msg) => self.postMessage({stderr: msg + "\n"}), // Add newline for better readability
stdin: () => {
console.warn("Input requested by Python code, but stdin is not supported in this playground.");
return "";
},
});
-self.onmessage = async (event) => {
+let pyodideReadyPromise: Promise | null = null;
+
+self.onmessage = async (event: MessageEvent) => {
+ 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[0]);
+ return;
+ }
+ // All other messages are code to run.
+ let code = event.data as string;
// make sure loading is done
const pyodide = await pyodideReadyPromise;
- const {id, code} = event.data;
// Now load any packages we need, run the code, and send the result back.
await pyodide.loadPackagesFromImports(code);
try {
- // Execute the python code in this context
- const result = await pyodide.runPythonAsync(code);
- self.postMessage({result, id});
+ self.postMessage({result: await pyodide.runPythonAsync(code)});
} catch (error: any) {
- self.postMessage({error: error.message, id});
+ self.postMessage({error: error.message});
}
};
\ No newline at end of file
diff --git a/package.json b/package.json
index dadad6e..21904ce 100644
--- a/package.json
+++ b/package.json
@@ -23,7 +23,9 @@
"@jamescoyle/vue-icon": "^0.1.2",
"@mdi/js": "^7.4.47",
"@mdi/svg": "^7.4.47",
+ "js-base64": "^3.7.7",
"monaco-editor": "^0.52.2",
+ "pako": "^2.1.0",
"pyodide": "^0.28.0",
"three": "^0.178.0",
"three-mesh-bvh": "^0.9.0",
@@ -34,6 +36,7 @@
"devDependencies": {
"@tsconfig/node20": "^20.1.4",
"@types/node": "^22.9.3",
+ "@types/pako": "^2.0.3",
"@types/three": "^0.178.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vitejs/plugin-vue-jsx": "^5.0.0",
@@ -45,7 +48,7 @@
"terser": "^5.36.0",
"typescript": "~5.8.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"
}
}
diff --git a/poetry.lock b/poetry.lock
index e09cad2..27ac2ca 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -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]]
name = "anytree"
@@ -6,6 +6,7 @@ version = "2.13.0"
description = "Powerful and Lightweight Python Tree Data Structure with various plugins"
optional = false
python-versions = "<4.0,>=3.9.2"
+groups = ["main"]
files = [
{file = "anytree-2.13.0-py3-none-any.whl", hash = "sha256:4cbcf10df36b1f1cba131b7e487ff3edafc9d6e932a3c70071b5b768bab901ff"},
{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"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2"},
{file = "asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7"},
@@ -32,6 +34,7 @@ version = "0.9.1"
description = "A python CAD programming library"
optional = false
python-versions = "<3.14,>=3.10"
+groups = ["main"]
files = [
{file = "build123d-0.9.1-py3-none-any.whl", hash = "sha256:0e2a3c171d7db55329201438a95ab888df6c1b0fd13067a3780efcc8fbe1cc78"},
{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"
optional = false
python-versions = "<3.14,>=3.10"
+groups = ["main"]
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_x86_64.whl", hash = "sha256:687c8a22d6248b28e44f136cecf93d274a897efbd0d8b36e38d4e207fecf1d84"},
@@ -91,10 +95,12 @@ version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+groups = ["main", "dev"]
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
+markers = {main = "sys_platform == \"win32\""}
[[package]]
name = "contourpy"
@@ -102,6 +108,7 @@ version = "1.3.2"
description = "Python library for calculating contours of 2D quadrilateral grids"
optional = false
python-versions = ">=3.10"
+groups = ["main"]
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_11_0_arm64.whl", hash = "sha256:dc41ba0714aa2968d1f8674ec97504a8f7e334f48eeacebcaa6256213acb0989"},
@@ -178,6 +185,7 @@ version = "0.12.1"
description = "Composable style cycles"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30"},
{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."
optional = false
python-versions = "<4.0,>=3.7"
+groups = ["main"]
files = [
{file = "dataclasses_json-0.6.7-py3-none-any.whl", hash = "sha256:0dbf33f26c8d5305befd61b39d2b3414e8a407bedc2834dea9b8d642666fb40a"},
{file = "dataclasses_json-0.6.7.tar.gz", hash = "sha256:b6b3e528266ea45b9535223bc53ca645f5208833c29229e847b3f26a1cc55fc0"},
@@ -208,6 +217,7 @@ version = "5.2.1"
description = "Decorators for Humans"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"},
{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."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+groups = ["main"]
files = [
{file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"},
{file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"},
@@ -228,7 +239,7 @@ files = [
wrapt = ">=1.10,<2"
[package.extras]
-dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"]
+dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"]
[[package]]
name = "exceptiongroup"
@@ -236,6 +247,8 @@ version = "1.3.0"
description = "Backport of PEP 654 (exception groups)"
optional = false
python-versions = ">=3.7"
+groups = ["main"]
+markers = "python_version == \"3.10\""
files = [
{file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"},
{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"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"},
{file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"},
]
[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]]
name = "ezdxf"
@@ -267,6 +281,7 @@ version = "1.4.2"
description = "A Python package to create/manipulate DXF drawings."
optional = false
python-versions = ">=3.9"
+groups = ["main"]
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_x86_64.whl", hash = "sha256:63aae1eb68aa30e1bbee38886e5b8bf602c9a4a0b672f0b2b31c5cdb29d6c533"},
@@ -330,6 +345,7 @@ version = "4.58.5"
description = "Tools to manipulate font files"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
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_x86_64.whl", hash = "sha256:b00530b84f87792891874938bd42f47af2f7f4c2a1d70466e6eb7166577853ab"},
@@ -376,18 +392,18 @@ files = [
]
[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)"]
-interpolatable = ["munkres", "pycairo", "scipy"]
+interpolatable = ["munkres ; platform_python_implementation == \"PyPy\"", "pycairo", "scipy ; platform_python_implementation != \"PyPy\""]
lxml = ["lxml (>=4.0)"]
pathops = ["skia-pathops (>=0.5.0)"]
plot = ["matplotlib"]
repacker = ["uharfbuzz (>=0.23.0)"]
symfont = ["sympy"]
-type1 = ["xattr"]
+type1 = ["xattr ; sys_platform == \"darwin\""]
ufo = ["fs (>=2.2.0,<3)"]
-unicode = ["unicodedata2 (>=15.1.0)"]
-woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
+unicode = ["unicodedata2 (>=15.1.0) ; python_version <= \"3.12\""]
+woff = ["brotli (>=1.0.1) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\"", "zopfli (>=0.1.4)"]
[[package]]
name = "ipython"
@@ -395,6 +411,7 @@ version = "8.37.0"
description = "IPython: Productive Interactive Computing"
optional = false
python-versions = ">=3.10"
+groups = ["main"]
files = [
{file = "ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2"},
{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]
all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"]
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"]
matplotlib = ["matplotlib"]
nbconvert = ["nbconvert"]
@@ -433,6 +450,7 @@ version = "0.19.2"
description = "An autocompletion tool for Python that can be used for text editors."
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"},
{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"
optional = false
python-versions = ">=3.10"
+groups = ["main"]
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_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."
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{file = "marshmallow-3.26.1-py3-none-any.whl", hash = "sha256:3350409f20a70a7e4e11a27661187b77cdcaeb20abca41c1454fe33636bea09c"},
{file = "marshmallow-3.26.1.tar.gz", hash = "sha256:e6d8affb6cb61d39d26402096dc0aee12d5a26d490a121f118d2e81dc0719dc6"},
@@ -560,6 +580,7 @@ version = "3.10.3"
description = "Python plotting package"
optional = false
python-versions = ">=3.10"
+groups = ["main"]
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_11_0_arm64.whl", hash = "sha256:d3bec61cb8221f0ca6313889308326e7bb303d0d302c5cc9e523b2f2e6c73deb"},
@@ -617,6 +638,7 @@ version = "0.1.7"
description = "Inline Matplotlib backend for Jupyter"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"},
{file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"},
@@ -625,12 +647,26 @@ files = [
[package.dependencies]
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]]
name = "mypy-extensions"
version = "1.1.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"},
{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"
optional = false
python-versions = ">=3.10"
+groups = ["main"]
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_11_0_arm64.whl", hash = "sha256:8e41fd67c52b86603a91c1a505ebaef50b3314de0213461c7a6e99c9a3beff90"},
@@ -706,6 +743,7 @@ version = "0.5.0"
description = ""
optional = false
python-versions = ">=3.10"
+groups = ["main"]
files = [
{file = "ocpsvg-0.5.0-py3-none-any.whl", hash = "sha256:68cafdc3d681a1707530360baf2d51cfd58414b7d439f42eafbd31e842cf295e"},
{file = "ocpsvg-0.5.0.tar.gz", hash = "sha256:5cd8dbec8bf590d373a82aaebeab241838185aab04ee2859f33b9d7956bbfba6"},
@@ -724,6 +762,7 @@ version = "25.0"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"},
{file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"},
@@ -735,6 +774,7 @@ version = "0.8.4"
description = "A Python Parser"
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"},
{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."
optional = false
python-versions = "*"
+groups = ["main"]
+markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
files = [
{file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"},
{file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"},
@@ -764,6 +806,7 @@ version = "11.3.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
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_11_0_arm64.whl", hash = "sha256:65dc69160114cdd0ca0f35cb434633c75e8e7fad4cf855177a05bf38678f73ad"},
@@ -879,15 +922,28 @@ fpx = ["olefile"]
mic = ["olefile"]
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)"]
-typing = ["typing-extensions"]
+typing = ["typing-extensions ; python_version < \"3.10\""]
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]]
name = "prompt-toolkit"
version = "3.0.51"
description = "Library for building powerful interactive command lines in Python"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "prompt_toolkit-3.0.51-py3-none-any.whl", hash = "sha256:52742911fde84e2d423e2f9a4cf1de7d7ac4e51958f648d9540e0fb8db077b07"},
{file = "prompt_toolkit-3.0.51.tar.gz", hash = "sha256:931a162e3b27fc90c86f1b48bb1fb2c528c2761475e57c9c06de13311c7b54ed"},
@@ -896,12 +952,45 @@ files = [
[package.dependencies]
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]]
name = "ptyprocess"
version = "0.7.0"
description = "Run a subprocess in a pseudo terminal"
optional = false
python-versions = "*"
+groups = ["main"]
+markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{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"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
{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"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{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."
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "pygltflib-1.16.4-py3-none-any.whl", hash = "sha256:1cb5886ca12ac512bbea3b5640f1e15575844418b1f1574de42ec19175e00f53"},
{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."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"},
{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"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{file = "pyparsing-3.2.3-py3-none-any.whl", hash = "sha256:a749938e02d6fd0b59b356ca504a24982314bb090c383e3cf201c95ef7e2bfcf"},
{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"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
files = [
{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"},
@@ -994,6 +1089,7 @@ version = "1.15.3"
description = "Fundamental algorithms for scientific computing in Python"
optional = false
python-versions = ">=3.10"
+groups = ["main"]
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_12_0_arm64.whl", hash = "sha256:ad3432cb0f9ed87477a8d97f03b763fd1d57709f1bbde3c9369b1dff5503b253"},
@@ -1049,7 +1145,7 @@ numpy = ">=1.23.5,<2.5"
[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"]
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]]
name = "six"
@@ -1057,6 +1153,7 @@ version = "1.17.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+groups = ["main"]
files = [
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{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"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"},
{file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"},
@@ -1087,6 +1185,7 @@ version = "1.9.6"
description = "Svg Elements Parsing"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "svgelements-1.9.6-py2.py3-none-any.whl", hash = "sha256:8a5cf2cc066d98e713d5b875b1d6e5eeb9b92e855e835ebd7caab2713ae1dcad"},
{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."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "svgpathtools-1.7.1-py2.py3-none-any.whl", hash = "sha256:3cbb8ba0e8d200f9639034608d9c55b68efbc1bef99ea99559a3e7cb024fb738"},
{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."
optional = false
python-versions = ">=3.6"
+groups = ["main"]
files = [
{file = "svgwrite-1.4.3-py3-none-any.whl", hash = "sha256:bb6b2b5450f1edbfa597d924f9ac2dd099e625562e492021d7dd614f65f8a22d"},
{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]]
name = "traitlets"
version = "5.14.3"
description = "Traitlets Python configuration system"
optional = false
python-versions = ">=3.8"
+groups = ["main"]
files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{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.)"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "trianglesolver-1.2-py3-none-any.whl", hash = "sha256:aa0903c3708b4e2b496f06d490cae72c6ff6274b00d1edce420fcfa3b2b76682"},
{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+"
optional = false
python-versions = ">=3.9"
+groups = ["main"]
files = [
{file = "typing_extensions-4.14.1-py3-none-any.whl", hash = "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76"},
{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."
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "typing_inspect-0.9.0-py3-none-any.whl", hash = "sha256:9ee6fc59062311ef8547596ab6b955e1b8aa46242d854bfc78f4f6b0eff35f9f"},
{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"
optional = false
python-versions = "*"
+groups = ["main"]
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_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"
optional = false
python-versions = "*"
+groups = ["main"]
files = [
{file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
{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."
optional = false
python-versions = ">=3.8"
+groups = ["main"]
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_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"},
@@ -1312,6 +1480,6 @@ files = [
]
[metadata]
-lock-version = "2.0"
+lock-version = "2.1"
python-versions = ">=3.10,<3.13"
-content-hash = "93d841ad150ea31f388ae73a764e7a0491e65ace01fa1bad7b66dbf6cba4b459"
+content-hash = "17564ce50af9a75e84337c83f77d86c9d2ff73a366de70ba27453e529bb468e9"
diff --git a/pyproject.toml b/pyproject.toml
index 080d229..5c11412 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,15 +1,24 @@
+[build-system]
+requires = ["poetry-core==2.1.3", "taskipy==1.14.1"]
+build-backend = "poetry.core.masonry.api"
+
[tool.poetry]
name = "yacv-server"
-version = "0.9.7"
+version = "0.10.0-alpha.4"
description = "Yet Another CAD Viewer (server)"
authors = ["Yeicor <4929005+Yeicor@users.noreply.github.com>"]
license = "MIT"
readme = "README.md"
include = [
- { path = 'yacv_server/frontend/*', format = 'wheel' },
- { path = 'yacv_server/frontend/*', format = 'sdist' },
+ { path = 'yacv_server/frontend/**/*', format = 'wheel' },
+ { 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]
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
pygltflib = "^1.16.2"
pillow = ">=10.2,<12.0"
+poetry-core = "==2.1.3"
-[tool.poetry.build]
-generate-setup-file = false
-script = "build.py"
-
-[build-system]
-requires = ["poetry-core"]
-build-backend = "poetry.core.masonry.api"
+[tool.poetry.group.dev.dependencies]
+taskipy = "^1.14.1"
diff --git a/vite.config.ts b/vite.config.ts
index c6e6910..ac53913 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,12 +1,17 @@
import {fileURLToPath, URL} from 'node:url'
import {defineConfig} from 'vite'
+// @ts-ignore
import vue from '@vitejs/plugin-vue'
+// @ts-ignore
import vueJsx from '@vitejs/plugin-vue-jsx'
import {name, version} from './package.json'
import {execSync} from 'child_process'
import {viteStaticCopy} from "vite-plugin-static-copy";
import {dirname, join} from "path";
+import {version as pyodideVersion} from "pyodide";
+
+let wantsSmallBuild = process.env.YACV_SMALL_BUILD == "true";
// https://vitejs.dev/config/
export default defineConfig({
@@ -15,7 +20,7 @@ export default defineConfig({
vue({
template: {
compilerOptions: {
- isCustomElement: tag => tag == 'model-viewer'
+ isCustomElement: (tag: string) => tag == 'model-viewer'
}
}
}),
@@ -37,8 +42,17 @@ export default defineConfig({
rollupOptions: {
output: {
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: {
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_GIT_SHA__: JSON.stringify(execSync('git rev-parse HEAD').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",
"!**/node_modules",
];
+ // @ts-ignore
const pyodideDir = dirname(fileURLToPath(import.meta.resolve("pyodide")));
return viteStaticCopy({
- targets: [
+ targets: wantsSmallBuild ? [] : [
{
src: [join(pyodideDir, "*")].concat(PYODIDE_EXCLUDE),
- dest: "assets",
+ dest: "pyodide-v" + pyodideVersion, // It would be better to use hashed names instead of folder...
},
],
});
diff --git a/yacv_server/pubsub.py b/yacv_server/pubsub.py
index 19f16e2..4da8416 100644
--- a/yacv_server/pubsub.py
+++ b/yacv_server/pubsub.py
@@ -54,8 +54,8 @@ class BufferedPubSub(Generic[T]):
self._subscribers.remove(q)
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) -> \
- Generator[T, None, None]:
+ def subscribe(self, include_buffered: bool = True, include_future: bool = True,
+ yield_timeout: float | None = 0.0) -> Generator[T, None, None]:
"""Subscribes to events as a generator that yields events and automatically unsubscribes"""
q = self._subscribe(include_buffered, include_future)
try:
diff --git a/yacv_server/tessellate.py b/yacv_server/tessellate.py
index 15b5bb0..c569c0a 100644
--- a/yacv_server/tessellate.py
+++ b/yacv_server/tessellate.py
@@ -3,8 +3,8 @@ from typing import List, Dict, Tuple, Optional
from OCP.BRep import BRep_Tool
from OCP.BRepAdaptor import BRepAdaptor_Curve
from OCP.GCPnts import GCPnts_TangentialDeflection
-from OCP.OCP.BRepLib import BRepLib_ToolTriangulatedShape
-from OCP.OCP.TopAbs import TopAbs_Orientation
+from OCP.BRepLib import BRepLib_ToolTriangulatedShape
+from OCP.TopAbs import TopAbs_Orientation
from OCP.TopLoc import TopLoc_Location
from OCP.TopoDS import TopoDS_Face, TopoDS_Edge, TopoDS_Shape, TopoDS_Vertex
from build123d import Vertex, Face, Location, Compound, Vector
diff --git a/yacv_server/yacv.py b/yacv_server/yacv.py
index 868f985..49876a6 100644
--- a/yacv_server/yacv.py
+++ b/yacv_server/yacv.py
@@ -8,6 +8,7 @@ import sys
import threading
import time
from dataclasses import dataclass
+from enum import Enum, auto
from http.server import ThreadingHTTPServer
from io import BytesIO
from threading import Thread
@@ -64,10 +65,20 @@ class UpdatesApiFullData(UpdatesApiData):
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:
"""The main yacv_server class, which manages the web server and the CAD objects."""
# 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]
"""The main thread running the server (will spawn other threads for each request)"""
server: Optional[ThreadingHTTPServer]
@@ -127,6 +138,10 @@ class YACV:
in the hexadecimal format #RRGGBB or #RRGGBBAA."""
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 = None
self.startup_complete = threading.Event()
@@ -144,6 +159,7 @@ class YACV:
def start(self):
"""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.startup_complete.is_set() is False, "Server already started"
# Start the server in a separate daemon thread
@@ -160,6 +176,8 @@ class YACV:
# noinspection PyUnusedLocal
def stop(self, *args):
"""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:
logger.error('Cannot stop server because it is not running')
return
@@ -167,7 +185,7 @@ class YACV:
# Inform the server that we are shutting down
self.shutting_down.set()
# 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
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):
sys.exit(0) # Exit with success
+ _yacvServerModelPrefix = "yacv_server://model/"
+
def _run_server(self):
"""Runs the web server"""
- logger.info('Starting server...')
+ logger.info('Starting server in %s mode...', self.protocol.name)
self.server = ThreadingHTTPServer(
(os.getenv('YACV_HOST', 'localhost'), int(os.getenv('YACV_PORT', 32323))),
lambda a, b, c: HTTPHandler(a, b, c, yacv=self))
@@ -206,6 +226,22 @@ class YACV:
self.startup_complete.set()
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):
"""
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
_kwargs = kwargs.copy()
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))
if not isinstance(obj, bytes):
obj = _preprocess_cad(obj, **_kwargs)
_hash = _hashcode(obj, **_kwargs)
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)
@@ -283,7 +319,7 @@ class YACV:
# Publish the remove event
show_event = copy.copy(show_events[-1])
show_event.is_remove = True
- self.show_events.publish(show_event)
+ self._show_event(show_event)
def clear(self, except_names: List[str] = None):
"""Clears all previously-shown objects from the scene"""
diff --git a/yarn.lock b/yarn.lock
index 77afa73..d2b3248 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1001,6 +1001,11 @@
dependencies:
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@*":
version "0.17.4"
resolved "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz"
@@ -1944,6 +1949,11 @@ jackspeak@^4.1.1:
dependencies:
"@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:
version "4.0.0"
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"
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:
version "1.0.1"
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"