From 4c7be17ddc7ab0efe14f8064495d2098af8ee8dc Mon Sep 17 00:00:00 2001 From: Yeicor <4929005+Yeicor@users.noreply.github.com> Date: Sun, 3 Mar 2024 10:56:44 +0100 Subject: [PATCH] add support for loading images as quads --- assets/img.jpg | Bin 0 -> 13698 bytes poetry.lock | 87 +++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + yacv_server/__init__.py | 10 ++++- yacv_server/cad.py | 69 ++++++++++++++++++++++++++++++- yacv_server/gltf.py | 11 +++-- yacv_server/server.py | 12 +++++- 7 files changed, 178 insertions(+), 12 deletions(-) create mode 100644 assets/img.jpg diff --git a/assets/img.jpg b/assets/img.jpg new file mode 100644 index 0000000000000000000000000000000000000000..609753f20cbdb93d22f37b6b0044848fc57240bf GIT binary patch literal 13698 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 UPR26kW$VRrh*k7S$iveA2V%?x6aWAK literal 0 HcmV?d00001 diff --git a/poetry.lock b/poetry.lock index ec29cf7..ffe9296 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1029,6 +1029,91 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" +[[package]] +name = "pillow" +version = "10.2.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pillow-10.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:7823bdd049099efa16e4246bdf15e5a13dbb18a51b68fa06d6c1d4d8b99a796e"}, + {file = "pillow-10.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:83b2021f2ade7d1ed556bc50a399127d7fb245e725aa0113ebd05cfe88aaf588"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fad5ff2f13d69b7e74ce5b4ecd12cc0ec530fcee76356cac6742785ff71c452"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da2b52b37dad6d9ec64e653637a096905b258d2fc2b984c41ae7d08b938a67e4"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:47c0995fc4e7f79b5cfcab1fc437ff2890b770440f7696a3ba065ee0fd496563"}, + {file = "pillow-10.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:322bdf3c9b556e9ffb18f93462e5f749d3444ce081290352c6070d014c93feb2"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:51f1a1bffc50e2e9492e87d8e09a17c5eea8409cda8d3f277eb6edc82813c17c"}, + {file = "pillow-10.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69ffdd6120a4737710a9eee73e1d2e37db89b620f702754b8f6e62594471dee0"}, + {file = "pillow-10.2.0-cp310-cp310-win32.whl", hash = "sha256:c6dafac9e0f2b3c78df97e79af707cdc5ef8e88208d686a4847bab8266870023"}, + {file = "pillow-10.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:aebb6044806f2e16ecc07b2a2637ee1ef67a11840a66752751714a0d924adf72"}, + {file = "pillow-10.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:7049e301399273a0136ff39b84c3678e314f2158f50f517bc50285fb5ec847ad"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:35bb52c37f256f662abdfa49d2dfa6ce5d93281d323a9af377a120e89a9eafb5"}, + {file = "pillow-10.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9c23f307202661071d94b5e384e1e1dc7dfb972a28a2310e4ee16103e66ddb67"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:773efe0603db30c281521a7c0214cad7836c03b8ccff897beae9b47c0b657d61"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11fa2e5984b949b0dd6d7a94d967743d87c577ff0b83392f17cb3990d0d2fd6e"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:716d30ed977be8b37d3ef185fecb9e5a1d62d110dfbdcd1e2a122ab46fddb03f"}, + {file = "pillow-10.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a086c2af425c5f62a65e12fbf385f7c9fcb8f107d0849dba5839461a129cf311"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c8de2789052ed501dd829e9cae8d3dcce7acb4777ea4a479c14521c942d395b1"}, + {file = "pillow-10.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:609448742444d9290fd687940ac0b57fb35e6fd92bdb65386e08e99af60bf757"}, + {file = "pillow-10.2.0-cp311-cp311-win32.whl", hash = "sha256:823ef7a27cf86df6597fa0671066c1b596f69eba53efa3d1e1cb8b30f3533068"}, + {file = "pillow-10.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1da3b2703afd040cf65ec97efea81cfba59cdbed9c11d8efc5ab09df9509fc56"}, + {file = "pillow-10.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:edca80cbfb2b68d7b56930b84a0e45ae1694aeba0541f798e908a49d66b837f1"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:1b5e1b74d1bd1b78bc3477528919414874748dd363e6272efd5abf7654e68bef"}, + {file = "pillow-10.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0eae2073305f451d8ecacb5474997c08569fb4eb4ac231ffa4ad7d342fdc25ac"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7c2286c23cd350b80d2fc9d424fc797575fb16f854b831d16fd47ceec078f2c"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e23412b5c41e58cec602f1135c57dfcf15482013ce6e5f093a86db69646a5aa"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:52a50aa3fb3acb9cf7213573ef55d31d6eca37f5709c69e6858fe3bc04a5c2a2"}, + {file = "pillow-10.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:127cee571038f252a552760076407f9cff79761c3d436a12af6000cd182a9d04"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8d12251f02d69d8310b046e82572ed486685c38f02176bd08baf216746eb947f"}, + {file = "pillow-10.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:54f1852cd531aa981bc0965b7d609f5f6cc8ce8c41b1139f6ed6b3c54ab82bfb"}, + {file = "pillow-10.2.0-cp312-cp312-win32.whl", hash = "sha256:257d8788df5ca62c980314053197f4d46eefedf4e6175bc9412f14412ec4ea2f"}, + {file = "pillow-10.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:154e939c5f0053a383de4fd3d3da48d9427a7e985f58af8e94d0b3c9fcfcf4f9"}, + {file = "pillow-10.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:f379abd2f1e3dddb2b61bc67977a6b5a0a3f7485538bcc6f39ec76163891ee48"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8373c6c251f7ef8bda6675dd6d2b3a0fcc31edf1201266b5cf608b62a37407f9"}, + {file = "pillow-10.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:870ea1ada0899fd0b79643990809323b389d4d1d46c192f97342eeb6ee0b8483"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4b6b1e20608493548b1f32bce8cca185bf0480983890403d3b8753e44077129"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3031709084b6e7852d00479fd1d310b07d0ba82765f973b543c8af5061cf990e"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:3ff074fc97dd4e80543a3e91f69d58889baf2002b6be64347ea8cf5533188213"}, + {file = "pillow-10.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:cb4c38abeef13c61d6916f264d4845fab99d7b711be96c326b84df9e3e0ff62d"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b1b3020d90c2d8e1dae29cf3ce54f8094f7938460fb5ce8bc5c01450b01fbaf6"}, + {file = "pillow-10.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:170aeb00224ab3dc54230c797f8404507240dd868cf52066f66a41b33169bdbe"}, + {file = "pillow-10.2.0-cp38-cp38-win32.whl", hash = "sha256:c4225f5220f46b2fde568c74fca27ae9771536c2e29d7c04f4fb62c83275ac4e"}, + {file = "pillow-10.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:0689b5a8c5288bc0504d9fcee48f61a6a586b9b98514d7d29b840143d6734f39"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b792a349405fbc0163190fde0dc7b3fef3c9268292586cf5645598b48e63dc67"}, + {file = "pillow-10.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c570f24be1e468e3f0ce7ef56a89a60f0e05b30a3669a459e419c6eac2c35364"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8ecd059fdaf60c1963c58ceb8997b32e9dc1b911f5da5307aab614f1ce5c2fb"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c365fd1703040de1ec284b176d6af5abe21b427cb3a5ff68e0759e1e313a5e7e"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:70c61d4c475835a19b3a5aa42492409878bbca7438554a1f89d20d58a7c75c01"}, + {file = "pillow-10.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b6f491cdf80ae540738859d9766783e3b3c8e5bd37f5dfa0b76abdecc5081f13"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d189550615b4948f45252d7f005e53c2040cea1af5b60d6f79491a6e147eef7"}, + {file = "pillow-10.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:49d9ba1ed0ef3e061088cd1e7538a0759aab559e2e0a80a36f9fd9d8c0c21591"}, + {file = "pillow-10.2.0-cp39-cp39-win32.whl", hash = "sha256:babf5acfede515f176833ed6028754cbcd0d206f7f614ea3447d67c33be12516"}, + {file = "pillow-10.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:0304004f8067386b477d20a518b50f3fa658a28d44e4116970abfcd94fac34a8"}, + {file = "pillow-10.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:0fb3e7fc88a14eacd303e90481ad983fd5b69c761e9e6ef94c983f91025da869"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:322209c642aabdd6207517e9739c704dc9f9db943015535783239022002f054a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3eedd52442c0a5ff4f887fab0c1c0bb164d8635b32c894bc1faf4c618dd89df2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb28c753fd5eb3dd859b4ee95de66cc62af91bcff5db5f2571d32a520baf1f04"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:33870dc4653c5017bf4c8873e5488d8f8d5f8935e2f1fb9a2208c47cdd66efd2"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3c31822339516fb3c82d03f30e22b1d038da87ef27b6a78c9549888f8ceda39a"}, + {file = "pillow-10.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:a2b56ba36e05f973d450582fb015594aaa78834fefe8dfb8fcd79b93e64ba4c6"}, + {file = "pillow-10.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d8e6aeb9201e655354b3ad049cb77d19813ad4ece0df1249d3c793de3774f8c7"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:2247178effb34a77c11c0e8ac355c7a741ceca0a732b27bf11e747bbc950722f"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15587643b9e5eb26c48e49a7b33659790d28f190fc514a322d55da2fb5c2950e"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753cd8f2086b2b80180d9b3010dd4ed147efc167c90d3bf593fe2af21265e5a5"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:7c8f97e8e7a9009bcacbe3766a36175056c12f9a44e6e6f2d5caad06dcfbf03b"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d1b35bcd6c5543b9cb547dee3150c93008f8dd0f1fef78fc0cd2b141c5baf58a"}, + {file = "pillow-10.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe4c15f6c9285dc54ce6553a3ce908ed37c8f3825b5a51a15c91442bb955b868"}, + {file = "pillow-10.2.0.tar.gz", hash = "sha256:e87f0b2c78157e12d7686b27d63c070fd65d994e8ddae6f328e0dcf4a0cd007e"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions"] +xmp = ["defusedxml"] + [[package]] name = "prompt-toolkit" version = "3.0.43" @@ -1611,4 +1696,4 @@ multidict = ">=4.0" [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "92566c9fc24a286eea553f28b583fe6de53d7ecb595be508a0ce4ae9ca9f58c6" +content-hash = "16d385dac0b8683b9d66b91696658ecf47a60fd19e08278f189c79858fe95563" diff --git a/pyproject.toml b/pyproject.toml index 0dc51a3..5a8ad1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,6 +20,7 @@ aiohttp-devtools = "^1.1.2" # Misc pygltflib = "^1.16.1" +pillow = "^10.2.0" [build-system] requires = ["poetry-core"] diff --git a/yacv_server/__init__.py b/yacv_server/__init__.py index 2463b78..7037103 100644 --- a/yacv_server/__init__.py +++ b/yacv_server/__init__.py @@ -3,6 +3,7 @@ import os import time from aiohttp import web +from build123d import Vector from server import Server @@ -18,6 +19,7 @@ if 'YACV_DISABLE_SERVER' not in os.environ: # Expose some nice aliases using the default server instance show = server.show show_object = show +show_image = server.show_image show_all = server.show_cad_all @@ -26,9 +28,13 @@ def _get_app() -> web.Application: logging.basicConfig(level=logging.DEBUG) from logo import build_logo from build123d import Axis - logo = build_logo(False) + logo = build_logo() server.show_cad(logo, 'Logo') - server.show_cad(logo.faces().group_by(Axis.X)[0].face().center_location, 'Location') + img_location = logo.faces().group_by(Axis.X)[0].face().center_location # Avoid overlapping: + img_location.position = Vector(img_location.position.X - 1e-2, img_location.position.Y, img_location.position.Z) + server.show_cad(img_location, 'Location') + img_path = os.path.join(os.path.dirname(__file__), '..', 'assets', 'img.jpg') + server.show_image(img_path, img_location, 20) return server.app diff --git a/yacv_server/cad.py b/yacv_server/cad.py index 05a1f56..f63dbfc 100644 --- a/yacv_server/cad.py +++ b/yacv_server/cad.py @@ -1,11 +1,14 @@ """ Utilities to work with CAD objects """ +import hashlib from typing import Optional, Union, List, Tuple from OCP.TopLoc import TopLoc_Location from OCP.TopoDS import TopoDS_Shape +from gltf import GLTFMgr + CADLike = Union[TopoDS_Shape, TopLoc_Location] # Faces, Edges, Vertices and Locations for now @@ -54,4 +57,68 @@ def grab_all_cad() -> List[Tuple[str, CADLike]]: shapes.append((key, shape)) return shapes -# TODO: Image to CAD utility and show_image shortcut on server. + +def image_to_gltf(source: str | bytes, center: any, ppmm: int, name: Optional[str] = None, + save_mime: str = 'image/jpeg') -> Tuple[bytes, str]: + """Convert an image to a GLTF CAD object, indicating the center location and pixels per millimeter.""" + from PIL import Image + import io + import os + from build123d import Plane + from build123d import Location + from build123d import Vector + + # Handle arguments + if name is None: + if isinstance(source, str): + name = os.path.basename(source) + else: + hasher = hashlib.md5() + hasher.update(source) + name = 'image_' + hasher.hexdigest() + format: str + if save_mime == 'image/jpeg': + format = 'JPEG' + elif save_mime == 'image/png': + format = 'PNG' + else: + raise ValueError(f'Unsupported save MIME type (for GLTF files): {save_mime}') + + # Get the plane of the image + center_loc = get_shape(center) + if not isinstance(center_loc, TopLoc_Location): + raise ValueError('Center location not valid') + plane = Plane(Location(center_loc)) + # Convert coordinates system + plane.origin = Vector(plane.origin.X, plane.origin.Z, -plane.origin.Y) + plane.z_dir = -plane.y_dir + plane.y_dir = plane.z_dir + + def vert(v: Vector) -> Tuple[float, float, float]: + return v.X, v.Y, v.Z + + # Load the image to a byte buffer + img = Image.open(source) + img_buf = io.BytesIO() + img.save(img_buf, format=format) + img_buf = img_buf.getvalue() + + # Build the gltf + mgr = GLTFMgr(image=(img_buf, save_mime)) + mgr.add_face([ + vert(plane.origin - plane.x_dir * img.width / (2 * ppmm) - plane.y_dir * img.height / (2 * ppmm)), + vert(plane.origin + plane.x_dir * img.width / (2 * ppmm) - plane.y_dir * img.height / (2 * ppmm)), + vert(plane.origin + plane.x_dir * img.width / (2 * ppmm) + plane.y_dir * img.height / (2 * ppmm)), + vert(plane.origin - plane.x_dir * img.width / (2 * ppmm) + plane.y_dir * img.height / (2 * ppmm)), + ], [ + (0, 2, 1), + (0, 3, 2), + ], [ + (0, 0), + (1, 0), + (1, 1), + (0, 1), + ]) + + # Return the GLTF binary blob and the suggested name of the image + return b''.join(mgr.gltf.save_to_bytes()), name diff --git a/yacv_server/gltf.py b/yacv_server/gltf.py index 73dbd0c..6248df7 100644 --- a/yacv_server/gltf.py +++ b/yacv_server/gltf.py @@ -12,7 +12,7 @@ _checkerboard_image_bytes = base64.decodebytes( class GLTFMgr: """A utility class to build our GLTF2 objects easily and incrementally""" - def __init__(self): + def __init__(self, image: Tuple[bytes, str] = (_checkerboard_image_bytes, 'image/png')): self.gltf = GLTF2( asset=Asset(generator=f"yacv_server@{importlib.metadata.version('yacv_server')}"), scene=0, @@ -20,14 +20,13 @@ class GLTFMgr: nodes=[Node(mesh=0)], meshes=[Mesh(primitives=[])], accessors=[], - bufferViews=[BufferView(buffer=0, byteLength=len(_checkerboard_image_bytes), byteOffset=0)], - buffers=[Buffer(byteLength=len(_checkerboard_image_bytes))], + bufferViews=[BufferView(buffer=0, byteLength=len(image[0]), byteOffset=0)], + buffers=[Buffer(byteLength=len(image[0]))], samplers=[Sampler(magFilter=NEAREST)], textures=[Texture(source=0, sampler=0)], - images=[Image(bufferView=0, mimeType='image/png')], + images=[Image(bufferView=0, mimeType=image[1])], ) - self.gltf.set_binary_blob(_checkerboard_image_bytes) - # TODO: Custom image support for loading textured planes as CAD objects + self.gltf.set_binary_blob(image[0]) def add_face(self, vertices_raw: List[Tuple[float, float, float]], indices_raw: List[Tuple[int, int, int]], tex_coord_raw: List[Tuple[float, float]]): diff --git a/yacv_server/server.py b/yacv_server/server.py index d4536e3..b90e9cf 100644 --- a/yacv_server/server.py +++ b/yacv_server/server.py @@ -15,7 +15,7 @@ from aiohttp import web from build123d import Shape, Axis, Location, Vector from dataclasses_json import dataclass_json -from cad import get_shape, grab_all_cad +from cad import get_shape, grab_all_cad, image_to_gltf from mylogger import logger from pubsub import BufferedPubSub from tessellate import _hashcode, tessellate @@ -190,7 +190,7 @@ class Server: self.show_cad(any_object, name, **kwargs) def show_gltf(self, gltf: bytes, name: Optional[str] = None, **kwargs): - """Publishes any single-file GLTF object to the server (GLB format recommended).""" + """Publishes any single-file GLTF object to the server.""" start = time.time() # Precompute the info and send it to the client as if it was a CAD object precomputed_info = self._show_common(name, _hashcode(gltf, **kwargs), start, kwargs=kwargs) @@ -200,6 +200,14 @@ class Server: publish_to.publish_nowait(b'') # Signal the end of the stream self.object_events[precomputed_info.name] = publish_to + def show_image(self, source: str | bytes, center: any, ppmm: int, name: Optional[str] = None, + save_mime: str = 'image/jpeg', **kwargs): + """Publishes an image as a quad GLTF object, indicating the center location and pixels per millimeter.""" + # Convert the image to a GLTF CAD object + gltf, name = image_to_gltf(source, center, ppmm, name, save_mime) + # Publish it like any other GLTF object + self.show_gltf(gltf, name, **kwargs) + def show_cad(self, obj: Union[TopoDS_Shape, any], name: Optional[str] = None, **kwargs): """Publishes a CAD object to the server""" start = time.time()