From f8473f065c911127f58e37b2dc68e42f930ac79f Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 12 Dec 2025 13:00:19 +0100 Subject: [PATCH 01/13] Implement Emscripten support with JavaScript bindings for `UvulaJS` - Added support for building JavaScript bindings through Emscripten. - Introduced `WITH_JS_BINDINGS` option in CMake for enabling/disabling JS bindings. - Updated `conanfile.py` to include new options, dependencies, and packaging logic for `UvulaJS`. - Integrated `UvulaJS` bindings for import/export with TypeScript support. - Modified GitHub actions to support WebAssembly (`platform_wasm`) and npm package workflows. NP-1250 --- .github/workflows/conan-package.yml | 10 + CMakeLists.txt | 20 +- UvulaJS/CMakeLists.txt | 16 + UvulaJS/UvulaJS.cpp | 437 ++++++++++++++++++++++++++++ conanfile.py | 22 ++ 5 files changed, 503 insertions(+), 2 deletions(-) create mode 100644 UvulaJS/CMakeLists.txt create mode 100644 UvulaJS/UvulaJS.cpp diff --git a/.github/workflows/conan-package.yml b/.github/workflows/conan-package.yml index f5b93d8..88e0bf0 100644 --- a/.github/workflows/conan-package.yml +++ b/.github/workflows/conan-package.yml @@ -15,6 +15,7 @@ on: - main - master - 'CURA-*' + - 'NP-*' - 'PP-*' - '[0-9].[0-9]*' - '[0-9].[0-9][0-9]*' @@ -25,4 +26,13 @@ on: jobs: conan-package: uses: ultimaker/cura-workflows/.github/workflows/conan-package.yml@main + with: + platform_wasm: true + secrets: inherit + + npm-package: + needs: [ conan-package ] + uses: ultimaker/cura-workflows/.github/workflows/npm-package.yml@main + with: + package_version_full: ${{ needs.conan-package.outputs.package_version_full }} secrets: inherit diff --git a/CMakeLists.txt b/CMakeLists.txt index eb079ff..c24ee28 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,11 @@ if (WITH_PYTHON_BINDINGS) message(STATUS "Configuring pyUvula version: ${PYUVULA_VERSION}") endif () +option(WITH_JS_BINDINGS "Build with JavaScript/Emscripten bindings: `UvulaJS`" OFF) +if (WITH_JS_BINDINGS) + message(STATUS "Configuring UvulaJS with Emscripten bindings") +endif () + if (NOT DEFINED UVULA_VERSION) message(FATAL_ERROR "UVULA_VERSION is not defined!") endif () @@ -51,8 +56,14 @@ target_compile_definitions(libuvula UVULA_VERSION="${UVULA_VERSION}" ) -use_threads(libuvula) -enable_sanitizers(libuvula) +if(NOT EMSCRIPTEN) + use_threads(libuvula) +endif() +if(EMSCRIPTEN) + # Skip sanitizers and threading for Emscripten builds to avoid shared memory issues +else() + enable_sanitizers(libuvula) +endif() if (${EXTENSIVE_WARNINGS}) set_project_warnings(libuvula) endif () @@ -62,6 +73,11 @@ if (WITH_PYTHON_BINDINGS) add_subdirectory(pyUvula) endif () +# --- Setup JavaScript/Emscripten bindings --- +if (WITH_JS_BINDINGS) + add_subdirectory(UvulaJS) +endif () + # --- Setup command line interface (for testing purposes) --- if (WITH_CLI) add_subdirectory(cli) diff --git a/UvulaJS/CMakeLists.txt b/UvulaJS/CMakeLists.txt new file mode 100644 index 0000000..d7076c4 --- /dev/null +++ b/UvulaJS/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(uvula_js UvulaJS.cpp) +target_link_options(uvula_js + PUBLIC + "SHELL:-s USE_ES6_IMPORT_META=1" + "SHELL:-s FORCE_FILESYSTEM=1" + "SHELL:-s EXPORT_NAME=uvula" + "SHELL:-s MODULARIZE=1" + "SHELL:-s EXPORT_ES6=1" + "SHELL:-s SINGLE_FILE=1" + "SHELL:-s ALLOW_MEMORY_GROWTH=1" + "SHELL:-s ERROR_ON_UNDEFINED_SYMBOLS=0" + "SHELL:--bind" + "SHELL:-l embind" + "SHELL: --emit-tsd uvula_js.d.ts" +) +target_link_libraries(uvula_js PUBLIC libuvula) \ No newline at end of file diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp new file mode 100644 index 0000000..e7c1c0f --- /dev/null +++ b/UvulaJS/UvulaJS.cpp @@ -0,0 +1,437 @@ +// (c) 2025, UltiMaker -- see LICENCE for details + +#include +#include +#include + +#include "Face.h" +#include "Matrix44F.h" +#include "Point2F.h" +#include "Point3F.h" +#include "Vector3F.h" +#include "project.h" +#include "unwrap.h" + +using namespace emscripten; + +// Info structure for library version information +struct uvula_info_t +{ + std::string uvula_version; + std::string git_hash; +}; + +// Parameter structure for project function +struct ProjectParams +{ + std::vector strokePolygon; + std::vector meshVertices; + std::vector meshIndices; + std::vector meshUV; + std::vector meshFacesConnectivity; + uint32_t textureWidth; + uint32_t textureHeight; + std::vector cameraProjectionMatrix; + bool isCameraPerspective; + uint32_t viewportWidth; + uint32_t viewportHeight; + std::vector cameraNormal; + uint32_t faceId; +}; + +uvula_info_t get_uvula_info() +{ + return { UVULA_VERSION, "" }; // Git hash can be added later if needed +} + +// Typed wrapper functions for better TypeScript generation +emscripten::val unwrapTyped(const std::vector& vertices, const std::vector& indices) +{ + // Convert flat arrays to Point3F and Face vectors + std::vector vertex_points; + std::vector face_indices; + + // Convert vertices (expecting flat array of [x1, y1, z1, x2, y2, z2, ...]) + vertex_points.reserve(vertices.size() / 3); + for (size_t i = 0; i < vertices.size(); i += 3) { + vertex_points.emplace_back(vertices[i], vertices[i + 1], vertices[i + 2]); + } + + // Convert indices (expecting flat array of [i1, i2, i3, i4, i5, i6, ...]) + face_indices.reserve(indices.size() / 3); + for (size_t i = 0; i < indices.size(); i += 3) { + face_indices.push_back({indices[i], indices[i + 1], indices[i + 2]}); + } + + // Prepare output + std::vector uv_coords(vertex_points.size(), {0.0f, 0.0f}); + uint32_t texture_width; + uint32_t texture_height; + + // Perform unwrapping + bool success = smartUnwrap(vertex_points, face_indices, uv_coords, texture_width, texture_height); + + if (!success) { + throw std::runtime_error("Couldn't unwrap UVs!"); + } + + // Convert result to JavaScript object + emscripten::val result = emscripten::val::object(); + std::vector uv_array; + uv_array.reserve(uv_coords.size() * 2); + + for (const auto& coord : uv_coords) { + uv_array.push_back(coord.x); + uv_array.push_back(coord.y); + } + + result.set("uvCoordinates", emscripten::val::array(uv_array)); + result.set("textureWidth", texture_width); + result.set("textureHeight", texture_height); + + return result; +} + +emscripten::val projectTyped( + const std::vector& stroke_polygon, + const std::vector& mesh_vertices, + const std::vector& mesh_indices, + const std::vector& mesh_uv, + const std::vector& mesh_faces_connectivity, + uint32_t texture_width, + uint32_t texture_height, + const std::vector& camera_projection_matrix, + bool is_camera_perspective, + uint32_t viewport_width, + uint32_t viewport_height, + const std::vector& camera_normal, + uint32_t face_id) +{ + // Convert stroke polygon + std::vector stroke_points; + stroke_points.reserve(stroke_polygon.size() / 2); + for (size_t i = 0; i < stroke_polygon.size(); i += 2) { + stroke_points.push_back({stroke_polygon[i], stroke_polygon[i + 1]}); + } + + // Convert mesh vertices + std::vector vertex_points; + vertex_points.reserve(mesh_vertices.size() / 3); + for (size_t i = 0; i < mesh_vertices.size(); i += 3) { + vertex_points.emplace_back(mesh_vertices[i], mesh_vertices[i + 1], mesh_vertices[i + 2]); + } + + // Convert mesh indices + std::vector face_indices; + face_indices.reserve(mesh_indices.size() / 3); + for (size_t i = 0; i < mesh_indices.size(); i += 3) { + face_indices.push_back({mesh_indices[i], mesh_indices[i + 1], mesh_indices[i + 2]}); + } + + // Convert mesh UV coordinates + std::vector uv_points; + uv_points.reserve(mesh_uv.size() / 2); + for (size_t i = 0; i < mesh_uv.size(); i += 2) { + uv_points.push_back({mesh_uv[i], mesh_uv[i + 1]}); + } + + // Convert mesh faces connectivity + std::vector connectivity; + connectivity.reserve(mesh_faces_connectivity.size() / 3); + for (size_t i = 0; i < mesh_faces_connectivity.size(); i += 3) { + connectivity.push_back({mesh_faces_connectivity[i], mesh_faces_connectivity[i + 1], mesh_faces_connectivity[i + 2]}); + } + + // Convert camera projection matrix (4x4 matrix as flat array) + float matrix_data[4][4]; + for (int i = 0; i < 16; ++i) { + matrix_data[i / 4][i % 4] = camera_projection_matrix[i]; + } + Matrix44F projection_matrix(matrix_data); + + // Convert camera normal (3-element array) + Vector3F normal(camera_normal[0], camera_normal[1], camera_normal[2]); + + // Call the projection function + std::vector result = doProject( + stroke_points, + vertex_points, + face_indices, + uv_points, + connectivity, + texture_width, + texture_height, + projection_matrix, + is_camera_perspective, + viewport_width, + viewport_height, + normal, + face_id + ); + + // Convert result to JavaScript array of arrays + emscripten::val js_result = emscripten::val::array(); + for (size_t i = 0; i < result.size(); ++i) { + std::vector polygon_flat; + const Polygon& polygon = result[i]; + polygon_flat.reserve(polygon.size() * 2); + for (const auto& point : polygon) { + polygon_flat.push_back(point.x); + polygon_flat.push_back(point.y); + } + js_result.set(i, emscripten::val::array(polygon_flat)); + } + + return js_result; +} + +// Structured wrapper for project function using ProjectParams +emscripten::val projectWithParams(const ProjectParams& params) +{ + return projectTyped( + params.strokePolygon, + params.meshVertices, + params.meshIndices, + params.meshUV, + params.meshFacesConnectivity, + params.textureWidth, + params.textureHeight, + params.cameraProjectionMatrix, + params.isCameraPerspective, + params.viewportWidth, + params.viewportHeight, + params.cameraNormal, + params.faceId + ); +} + +// Wrapper for the unwrap function that works with JavaScript arrays +emscripten::val jsUnwrap(const emscripten::val& vertices_js, const emscripten::val& indices_js) +{ + // Convert JavaScript arrays to C++ vectors + std::vector vertices; + std::vector indices; + + // Get array lengths + unsigned vertices_length = vertices_js["length"].as(); + unsigned indices_length = indices_js["length"].as(); + + // Convert vertices (expecting flat array of [x1, y1, z1, x2, y2, z2, ...]) + vertices.reserve(vertices_length / 3); + for (unsigned i = 0; i < vertices_length; i += 3) { + float x = vertices_js[i].as(); + float y = vertices_js[i + 1].as(); + float z = vertices_js[i + 2].as(); + vertices.emplace_back(x, y, z); + } + + // Convert indices (expecting flat array of [i1, i2, i3, i4, i5, i6, ...]) + indices.reserve(indices_length / 3); + for (unsigned i = 0; i < indices_length; i += 3) { + uint32_t i1 = indices_js[i].as(); + uint32_t i2 = indices_js[i + 1].as(); + uint32_t i3 = indices_js[i + 2].as(); + indices.push_back({i1, i2, i3}); + } + + // Prepare output + std::vector uv_coords(vertices.size(), {0.0f, 0.0f}); + uint32_t texture_width; + uint32_t texture_height; + + // Perform unwrapping + bool success = smartUnwrap(vertices, indices, uv_coords, texture_width, texture_height); + + if (!success) { + throw std::runtime_error("Couldn't unwrap UVs!"); + } + + // Convert result to JavaScript object + emscripten::val result = emscripten::val::object(); + emscripten::val uv_array = emscripten::val::array(); + + for (size_t i = 0; i < uv_coords.size(); ++i) { + uv_array.set(i * 2, uv_coords[i].x); + uv_array.set(i * 2 + 1, uv_coords[i].y); + } + + result.set("uvCoordinates", uv_array); + result.set("textureWidth", texture_width); + result.set("textureHeight", texture_height); + + return result; +} + +// Wrapper for the project function that works with JavaScript arrays +emscripten::val jsProject( + const emscripten::val& stroke_polygon_js, + const emscripten::val& mesh_vertices_js, + const emscripten::val& mesh_indices_js, + const emscripten::val& mesh_uv_js, + const emscripten::val& mesh_faces_connectivity_js, + uint32_t texture_width, + uint32_t texture_height, + const emscripten::val& camera_projection_matrix_js, + bool is_camera_perspective, + uint32_t viewport_width, + uint32_t viewport_height, + const emscripten::val& camera_normal_js, + uint32_t face_id) +{ + // Convert stroke polygon (flat array of [x1, y1, x2, y2, ...]) + std::vector stroke_polygon; + unsigned stroke_length = stroke_polygon_js["length"].as(); + stroke_polygon.reserve(stroke_length / 2); + for (unsigned i = 0; i < stroke_length; i += 2) { + float x = stroke_polygon_js[i].as(); + float y = stroke_polygon_js[i + 1].as(); + stroke_polygon.push_back({x, y}); + } + + // Convert mesh vertices (flat array of [x1, y1, z1, x2, y2, z2, ...]) + std::vector mesh_vertices; + unsigned vertices_length = mesh_vertices_js["length"].as(); + mesh_vertices.reserve(vertices_length / 3); + for (unsigned i = 0; i < vertices_length; i += 3) { + float x = mesh_vertices_js[i].as(); + float y = mesh_vertices_js[i + 1].as(); + float z = mesh_vertices_js[i + 2].as(); + mesh_vertices.emplace_back(x, y, z); + } + + // Convert mesh indices (flat array of [i1, i2, i3, i4, i5, i6, ...]) + std::vector mesh_indices; + unsigned indices_length = mesh_indices_js["length"].as(); + mesh_indices.reserve(indices_length / 3); + for (unsigned i = 0; i < indices_length; i += 3) { + uint32_t i1 = mesh_indices_js[i].as(); + uint32_t i2 = mesh_indices_js[i + 1].as(); + uint32_t i3 = mesh_indices_js[i + 2].as(); + mesh_indices.push_back({i1, i2, i3}); + } + + // Convert mesh UV coordinates (flat array of [u1, v1, u2, v2, ...]) + std::vector mesh_uv; + unsigned uv_length = mesh_uv_js["length"].as(); + mesh_uv.reserve(uv_length / 2); + for (unsigned i = 0; i < uv_length; i += 2) { + float u = mesh_uv_js[i].as(); + float v = mesh_uv_js[i + 1].as(); + mesh_uv.push_back({u, v}); + } + + // Convert mesh faces connectivity (flat array of [f1, f2, f3, f4, f5, f6, ...]) + std::vector mesh_faces_connectivity; + unsigned connectivity_length = mesh_faces_connectivity_js["length"].as(); + mesh_faces_connectivity.reserve(connectivity_length / 3); + for (unsigned i = 0; i < connectivity_length; i += 3) { + int32_t i1 = mesh_faces_connectivity_js[i].as(); + int32_t i2 = mesh_faces_connectivity_js[i + 1].as(); + int32_t i3 = mesh_faces_connectivity_js[i + 2].as(); + mesh_faces_connectivity.push_back({i1, i2, i3}); + } + + // Convert camera projection matrix (4x4 matrix as flat array) + float matrix_data[4][4]; + for (int i = 0; i < 16; ++i) { + matrix_data[i / 4][i % 4] = camera_projection_matrix_js[i].as(); + } + Matrix44F camera_projection_matrix(matrix_data); + + // Convert camera normal (3-element array) + float normal_x = camera_normal_js[0].as(); + float normal_y = camera_normal_js[1].as(); + float normal_z = camera_normal_js[2].as(); + Vector3F camera_normal(normal_x, normal_y, normal_z); + + // Call the projection function + std::vector result = doProject( + stroke_polygon, + mesh_vertices, + mesh_indices, + mesh_uv, + mesh_faces_connectivity, + texture_width, + texture_height, + camera_projection_matrix, + is_camera_perspective, + viewport_width, + viewport_height, + camera_normal, + face_id + ); + + // Convert result to JavaScript array of arrays + emscripten::val js_result = emscripten::val::array(); + for (size_t i = 0; i < result.size(); ++i) { + emscripten::val polygon_array = emscripten::val::array(); + const Polygon& polygon = result[i]; + for (size_t j = 0; j < polygon.size(); ++j) { + polygon_array.set(j * 2, polygon[j].x); + polygon_array.set(j * 2 + 1, polygon[j].y); + } + js_result.set(i, polygon_array); + } + + return js_result; +} + +EMSCRIPTEN_BINDINGS(uvula) +{ + // Register array types for better TypeScript generation + register_vector("FloatArray"); + register_vector("IntArray"); + register_vector("UInt32Array"); + + // Register ProjectParams structure + value_object("ProjectParams") + .field("strokePolygon", &ProjectParams::strokePolygon) + .field("meshVertices", &ProjectParams::meshVertices) + .field("meshIndices", &ProjectParams::meshIndices) + .field("meshUV", &ProjectParams::meshUV) + .field("meshFacesConnectivity", &ProjectParams::meshFacesConnectivity) + .field("textureWidth", &ProjectParams::textureWidth) + .field("textureHeight", &ProjectParams::textureHeight) + .field("cameraProjectionMatrix", &ProjectParams::cameraProjectionMatrix) + .field("isCameraPerspective", &ProjectParams::isCameraPerspective) + .field("viewportWidth", &ProjectParams::viewportWidth) + .field("viewportHeight", &ProjectParams::viewportHeight) + .field("cameraNormal", &ProjectParams::cameraNormal) + .field("faceId", &ProjectParams::faceId); + + // Main typed functions with proper TypeScript signatures + function("unwrap", &unwrapTyped); + function("project", &projectWithParams); + + // Version information + value_object("uvula_info_t") + .field("uvula_version", &uvula_info_t::uvula_version) + .field("git_hash", &uvula_info_t::git_hash); + + function("uvula_info", &get_uvula_info); + + // Utility classes for direct access if needed + class_("Point2F") + .constructor<>() + .property("x", &Point2F::x) + .property("y", &Point2F::y); + + class_("Point3F") + .constructor() + .function("x", &Point3F::x) + .function("y", &Point3F::y) + .function("z", &Point3F::z); + + class_("Vector3F") + .constructor(); + + value_object("Face") + .field("i1", &Face::i1) + .field("i2", &Face::i2) + .field("i3", &Face::i3); + + value_object("FaceSigned") + .field("i1", &FaceSigned::i1) + .field("i2", &FaceSigned::i2) + .field("i3", &FaceSigned::i3); +} \ No newline at end of file diff --git a/conanfile.py b/conanfile.py index 645a6f0..d813fec 100644 --- a/conanfile.py +++ b/conanfile.py @@ -23,12 +23,14 @@ class UvulaConan(ConanFile): url = "https://github.com/Ultimaker/libUvula" homepage = "https://ultimaker.com" package_type = "library" + python_requires = "npmpackage/[>=1.0.0]" settings = "os", "arch", "compiler", "build_type" options = { "shared": [True, False], "fPIC": [True, False], "enable_extensive_warnings": [True, False], "with_python_bindings": [True, False], + "with_js_bindings": [True, False], "with_cli": [True, False], } default_options = { @@ -36,6 +38,7 @@ class UvulaConan(ConanFile): "fPIC": True, "enable_extensive_warnings": False, "with_python_bindings": True, + "with_js_bindings": False, "with_cli": False, } @@ -66,12 +69,14 @@ def export_sources(self): copy(self, "*", os.path.join(self.recipe_folder, "include"), os.path.join(self.export_sources_folder, "include")) copy(self, "*", os.path.join(self.recipe_folder, "pyUvula"), os.path.join(self.export_sources_folder, "pyUvula")) + copy(self, "*", os.path.join(self.recipe_folder, "UvulaJS"), os.path.join(self.export_sources_folder, "UvulaJS")) def config_options(self): if self.settings.os == "Windows": del self.options.fPIC if self.settings.arch == "wasm" and self.settings.os == "Emscripten": del self.options.with_python_bindings + self.options.with_js_bindings = True def configure(self): if self.options.get_safe("with_python_bindings", False): @@ -88,6 +93,11 @@ def layout(self): self.layouts.build.runenv_info.prepend_path("PYTHONPATH", "pyUvula") self.layouts.package.runenv_info.prepend_path("PYTHONPATH", os.path.join("lib", "pyUvula")) + if self.settings.os == "Emscripten": + self.cpp.build.bin = ["uvula_js.js"] + self.cpp.package.bin = ["uvula_js.js"] + self.cpp.build.bindirs += ["uvula_js"] + def requirements(self): self.requires("spdlog/1.15.1") @@ -125,6 +135,11 @@ def generate(self): if self.options.get_safe("with_python_bindings", False): tc.variables["PYUVULA_VERSION"] = self.version + if self.settings.arch == "wasm" and self.settings.os == "Emscripten": + tc.variables["WITH_JS_BINDINGS"] = True + else: + tc.variables["WITH_JS_BINDINGS"] = self.options.get_safe("with_js_bindings", False) + tc.variables["WITH_CLI"] = self.options.get_safe("with_cli", False) if is_msvc(self): @@ -150,9 +165,13 @@ def build(self): cmake.configure() cmake.build() + def deploy(self): + copy(self, "uvula_js*", src=os.path.join(self.package_folder, "lib"), dst=self.deploy_folder) + def package(self): copy(self, pattern="LICENSE", dst=os.path.join(self.package_folder, "licenses"), src=self.source_folder) copy(self, "*.pyd", src = os.path.join(self.build_folder, "pyUvula"), dst = os.path.join(self.package_folder, "lib", "pyUvula"), keep_path = False) + copy(self, pattern="uvula_js.*", src=os.path.join(self.build_folder, "UvulaJS"), dst=os.path.join(self.package_folder, "lib")) copy(self, f"*.d.ts", src=self.build_folder, dst=os.path.join(self.package_folder, "bin"), keep_path = False) copy(self, f"*.js", src=self.build_folder, dst=os.path.join(self.package_folder, "bin"), keep_path = False) packager = AutoPackager(self) @@ -162,3 +181,6 @@ def package_info(self): if self.options.get_safe("with_python_bindings", False): self.conf_info.define("user.uvula:pythonpath", os.path.join(self.package_folder, "lib", "pyUvula")) + + if self.settings.os == "Emscripten": + self.python_requires["npmpackage"].module.conf_package_json(self) From 1737a91b9e7945d137615fcf0219a8427b1da3a6 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 12 Dec 2025 13:54:24 +0100 Subject: [PATCH 02/13] =?UTF-8?q?Update=20GitHub=20workflow=20naming:=20`c?= =?UTF-8?q?onan-package.yml`=20=E2=86=92=20`package.yml`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NP-1250 --- .github/workflows/{conan-package.yml => package.yml} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{conan-package.yml => package.yml} (92%) diff --git a/.github/workflows/conan-package.yml b/.github/workflows/package.yml similarity index 92% rename from .github/workflows/conan-package.yml rename to .github/workflows/package.yml index 88e0bf0..1a5db7f 100644 --- a/.github/workflows/conan-package.yml +++ b/.github/workflows/package.yml @@ -1,4 +1,4 @@ -name: conan-package +name: package on: push: @@ -10,7 +10,7 @@ on: - 'conanfile.py' - 'conandata.yml' - 'CMakeLists.txt' - - '.github/workflows/conan-package.yml' + - 'package.yml' branches: - main - master From d3f77fec84b71aa0a42d2befcd0d7fe0f036ecb6 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 12 Dec 2025 14:04:45 +0100 Subject: [PATCH 03/13] Fix workflow path trigger in package.yml to include correct location NP-1250 --- .github/workflows/package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 1a5db7f..86326e2 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -10,7 +10,7 @@ on: - 'conanfile.py' - 'conandata.yml' - 'CMakeLists.txt' - - 'package.yml' + - '.github/workflows/package.yml' branches: - main - master From 77e4a0d8f0e5751f78c6786b288c5088a58f8562 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Tue, 16 Dec 2025 11:23:32 +0100 Subject: [PATCH 04/13] Refactor UvulaJS JavaScript bindings for TypeScript integration - Replaced custom structures with standardized TypeScript-like types (Float32Array, Uint32Array, Int32Array). - Refactored wrappers and return types to improve type safety in TypeScript. - Enhanced `project` and `unwrap` methods with new structured return types. - Updated Emscripten bindings to support TypeScript-friendly structures and arrays. NP-1250 --- UvulaJS/UvulaJS.cpp | 227 +++++++++++++++++++++++++++----------------- 1 file changed, 139 insertions(+), 88 deletions(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index e7c1c0f..53832c6 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -14,53 +14,75 @@ using namespace emscripten; -// Info structure for library version information -struct uvula_info_t +// TypeScript type aliases for better type safety +EMSCRIPTEN_DECLARE_VAL_TYPE(Float32Array); +EMSCRIPTEN_DECLARE_VAL_TYPE(Uint32Array); +EMSCRIPTEN_DECLARE_VAL_TYPE(Int32Array); + +// Return type for unwrap function +struct UnwrapResult +{ + std::vector uvCoordinates; + uint32_t textureWidth; + uint32_t textureHeight; +}; + +// Return type for project function (array of polygons as flat arrays) +struct ProjectResult { - std::string uvula_version; - std::string git_hash; + std::vector> polygons; }; // Parameter structure for project function struct ProjectParams { - std::vector strokePolygon; - std::vector meshVertices; - std::vector meshIndices; - std::vector meshUV; - std::vector meshFacesConnectivity; - uint32_t textureWidth; - uint32_t textureHeight; - std::vector cameraProjectionMatrix; - bool isCameraPerspective; - uint32_t viewportWidth; - uint32_t viewportHeight; - std::vector cameraNormal; - uint32_t faceId; + Float32Array strokePolygon = Float32Array{emscripten::val::array()}; // float[] + Float32Array meshVertices = Float32Array{emscripten::val::array()}; // float[] + Uint32Array meshIndices = Uint32Array{emscripten::val::array()}; // uint32_t[] + Float32Array meshUV = Float32Array{emscripten::val::array()}; // float[] + Int32Array meshFacesConnectivity = Int32Array{emscripten::val::array()}; // int32_t[] + uint32_t textureWidth = 0; + uint32_t textureHeight = 0; + Float32Array cameraProjectionMatrix = Float32Array{emscripten::val::array()}; // float[16] - 4x4 matrix + bool isCameraPerspective = false; + uint32_t viewportWidth = 0; + uint32_t viewportHeight = 0; + Float32Array cameraNormal = Float32Array{emscripten::val::array()}; // float[3] - normal vector + uint32_t faceId = 0; }; -uvula_info_t get_uvula_info() +std::string get_uvula_info() { - return { UVULA_VERSION, "" }; // Git hash can be added later if needed + return { UVULA_VERSION }; } // Typed wrapper functions for better TypeScript generation -emscripten::val unwrapTyped(const std::vector& vertices, const std::vector& indices) +UnwrapResult unwrapTyped(const Float32Array& vertices_js, const Uint32Array& indices_js) { - // Convert flat arrays to Point3F and Face vectors + // Convert JavaScript arrays to C++ vectors std::vector vertex_points; std::vector face_indices; - + + // Get array lengths + unsigned vertices_length = vertices_js["length"].as(); + unsigned indices_length = indices_js["length"].as(); + // Convert vertices (expecting flat array of [x1, y1, z1, x2, y2, z2, ...]) - vertex_points.reserve(vertices.size() / 3); - for (size_t i = 0; i < vertices.size(); i += 3) { - vertex_points.emplace_back(vertices[i], vertices[i + 1], vertices[i + 2]); + vertex_points.reserve(vertices_length / 3); + for (unsigned i = 0; i < vertices_length; i += 3) { + float x = vertices_js[i].as(); + float y = vertices_js[i + 1].as(); + float z = vertices_js[i + 2].as(); + vertex_points.emplace_back(x, y, z); } // Convert indices (expecting flat array of [i1, i2, i3, i4, i5, i6, ...]) - face_indices.reserve(indices.size() / 3); - for (size_t i = 0; i < indices.size(); i += 3) { - face_indices.push_back({indices[i], indices[i + 1], indices[i + 2]}); + face_indices.reserve(indices_length / 3); + for (unsigned i = 0; i < indices_length; i += 3) { + uint32_t i1 = indices_js[i].as(); + uint32_t i2 = indices_js[i + 1].as(); + uint32_t i3 = indices_js[i + 2].as(); + face_indices.push_back({i1, i2, i3}); } // Prepare output @@ -70,87 +92,107 @@ emscripten::val unwrapTyped(const std::vector& vertices, const std::vecto // Perform unwrapping bool success = smartUnwrap(vertex_points, face_indices, uv_coords, texture_width, texture_height); - + if (!success) { throw std::runtime_error("Couldn't unwrap UVs!"); } - // Convert result to JavaScript object - emscripten::val result = emscripten::val::object(); + // Convert result to structured return type std::vector uv_array; uv_array.reserve(uv_coords.size() * 2); - + for (const auto& coord : uv_coords) { uv_array.push_back(coord.x); uv_array.push_back(coord.y); } - - result.set("uvCoordinates", emscripten::val::array(uv_array)); - result.set("textureWidth", texture_width); - result.set("textureHeight", texture_height); - - return result; + + return UnwrapResult{ + .uvCoordinates = uv_array, + .textureWidth = texture_width, + .textureHeight = texture_height + }; } -emscripten::val projectTyped( - const std::vector& stroke_polygon, - const std::vector& mesh_vertices, - const std::vector& mesh_indices, - const std::vector& mesh_uv, - const std::vector& mesh_faces_connectivity, +ProjectResult projectTyped( + const Float32Array& stroke_polygon_js, + const Float32Array& mesh_vertices_js, + const Uint32Array& mesh_indices_js, + const Float32Array& mesh_uv_js, + const Int32Array& mesh_faces_connectivity_js, uint32_t texture_width, uint32_t texture_height, - const std::vector& camera_projection_matrix, + const Float32Array& camera_projection_matrix_js, bool is_camera_perspective, uint32_t viewport_width, uint32_t viewport_height, - const std::vector& camera_normal, + const Float32Array& camera_normal_js, uint32_t face_id) { - // Convert stroke polygon + // Convert stroke polygon (flat array of [x1, y1, x2, y2, ...]) std::vector stroke_points; - stroke_points.reserve(stroke_polygon.size() / 2); - for (size_t i = 0; i < stroke_polygon.size(); i += 2) { - stroke_points.push_back({stroke_polygon[i], stroke_polygon[i + 1]}); + unsigned stroke_length = stroke_polygon_js["length"].as(); + stroke_points.reserve(stroke_length / 2); + for (unsigned i = 0; i < stroke_length; i += 2) { + float x = stroke_polygon_js[i].as(); + float y = stroke_polygon_js[i + 1].as(); + stroke_points.push_back({x, y}); } - // Convert mesh vertices + // Convert mesh vertices (flat array of [x1, y1, z1, x2, y2, z2, ...]) std::vector vertex_points; - vertex_points.reserve(mesh_vertices.size() / 3); - for (size_t i = 0; i < mesh_vertices.size(); i += 3) { - vertex_points.emplace_back(mesh_vertices[i], mesh_vertices[i + 1], mesh_vertices[i + 2]); + unsigned vertices_length = mesh_vertices_js["length"].as(); + vertex_points.reserve(vertices_length / 3); + for (unsigned i = 0; i < vertices_length; i += 3) { + float x = mesh_vertices_js[i].as(); + float y = mesh_vertices_js[i + 1].as(); + float z = mesh_vertices_js[i + 2].as(); + vertex_points.emplace_back(x, y, z); } - // Convert mesh indices + // Convert mesh indices (flat array of [i1, i2, i3, i4, i5, i6, ...]) std::vector face_indices; - face_indices.reserve(mesh_indices.size() / 3); - for (size_t i = 0; i < mesh_indices.size(); i += 3) { - face_indices.push_back({mesh_indices[i], mesh_indices[i + 1], mesh_indices[i + 2]}); + unsigned indices_length = mesh_indices_js["length"].as(); + face_indices.reserve(indices_length / 3); + for (unsigned i = 0; i < indices_length; i += 3) { + uint32_t i1 = mesh_indices_js[i].as(); + uint32_t i2 = mesh_indices_js[i + 1].as(); + uint32_t i3 = mesh_indices_js[i + 2].as(); + face_indices.push_back({i1, i2, i3}); } - // Convert mesh UV coordinates + // Convert mesh UV coordinates (flat array of [u1, v1, u2, v2, ...]) std::vector uv_points; - uv_points.reserve(mesh_uv.size() / 2); - for (size_t i = 0; i < mesh_uv.size(); i += 2) { - uv_points.push_back({mesh_uv[i], mesh_uv[i + 1]}); + unsigned uv_length = mesh_uv_js["length"].as(); + uv_points.reserve(uv_length / 2); + for (unsigned i = 0; i < uv_length; i += 2) { + float u = mesh_uv_js[i].as(); + float v = mesh_uv_js[i + 1].as(); + uv_points.push_back({u, v}); } - // Convert mesh faces connectivity + // Convert mesh faces connectivity (flat array of [f1, f2, f3, f4, f5, f6, ...]) std::vector connectivity; - connectivity.reserve(mesh_faces_connectivity.size() / 3); - for (size_t i = 0; i < mesh_faces_connectivity.size(); i += 3) { - connectivity.push_back({mesh_faces_connectivity[i], mesh_faces_connectivity[i + 1], mesh_faces_connectivity[i + 2]}); + unsigned connectivity_length = mesh_faces_connectivity_js["length"].as(); + connectivity.reserve(connectivity_length / 3); + for (unsigned i = 0; i < connectivity_length; i += 3) { + int32_t i1 = mesh_faces_connectivity_js[i].as(); + int32_t i2 = mesh_faces_connectivity_js[i + 1].as(); + int32_t i3 = mesh_faces_connectivity_js[i + 2].as(); + connectivity.push_back({i1, i2, i3}); } // Convert camera projection matrix (4x4 matrix as flat array) float matrix_data[4][4]; for (int i = 0; i < 16; ++i) { - matrix_data[i / 4][i % 4] = camera_projection_matrix[i]; + matrix_data[i / 4][i % 4] = camera_projection_matrix_js[i].as(); } Matrix44F projection_matrix(matrix_data); // Convert camera normal (3-element array) - Vector3F normal(camera_normal[0], camera_normal[1], camera_normal[2]); + float normal_x = camera_normal_js[0].as(); + float normal_y = camera_normal_js[1].as(); + float normal_z = camera_normal_js[2].as(); + Vector3F normal(normal_x, normal_y, normal_z); // Call the projection function std::vector result = doProject( @@ -169,24 +211,25 @@ emscripten::val projectTyped( face_id ); - // Convert result to JavaScript array of arrays - emscripten::val js_result = emscripten::val::array(); - for (size_t i = 0; i < result.size(); ++i) { + // Convert result to structured return type + std::vector> result_polygons; + result_polygons.reserve(result.size()); + + for (const auto& polygon : result) { std::vector polygon_flat; - const Polygon& polygon = result[i]; polygon_flat.reserve(polygon.size() * 2); for (const auto& point : polygon) { polygon_flat.push_back(point.x); polygon_flat.push_back(point.y); } - js_result.set(i, emscripten::val::array(polygon_flat)); + result_polygons.push_back(std::move(polygon_flat)); } - return js_result; + return ProjectResult{.polygons = result_polygons}; } // Structured wrapper for project function using ProjectParams -emscripten::val projectWithParams(const ProjectParams& params) +ProjectResult projectWithParams(const ProjectParams& params) { return projectTyped( params.strokePolygon, @@ -241,7 +284,7 @@ emscripten::val jsUnwrap(const emscripten::val& vertices_js, const emscripten::v // Perform unwrapping bool success = smartUnwrap(vertices, indices, uv_coords, texture_width, texture_height); - + if (!success) { throw std::runtime_error("Couldn't unwrap UVs!"); } @@ -249,16 +292,16 @@ emscripten::val jsUnwrap(const emscripten::val& vertices_js, const emscripten::v // Convert result to JavaScript object emscripten::val result = emscripten::val::object(); emscripten::val uv_array = emscripten::val::array(); - + for (size_t i = 0; i < uv_coords.size(); ++i) { uv_array.set(i * 2, uv_coords[i].x); uv_array.set(i * 2 + 1, uv_coords[i].y); } - + result.set("uvCoordinates", uv_array); result.set("textureWidth", texture_width); result.set("textureHeight", texture_height); - + return result; } @@ -378,10 +421,23 @@ emscripten::val jsProject( EMSCRIPTEN_BINDINGS(uvula) { - // Register array types for better TypeScript generation - register_vector("FloatArray"); - register_vector("IntArray"); - register_vector("UInt32Array"); + // Register typed array types + register_vector("Float32Array"); + register_vector>("Float32ArrayArray"); + + // Register TypeScript-style typed arrays + emscripten::register_type("Float32Array"); + emscripten::register_type("Uint32Array"); + emscripten::register_type("Int32Array"); + + // Register structured return types + value_object("UnwrapResult") + .field("uvCoordinates", &UnwrapResult::uvCoordinates) + .field("textureWidth", &UnwrapResult::textureWidth) + .field("textureHeight", &UnwrapResult::textureHeight); + + value_object("ProjectResult") + .field("polygons", &ProjectResult::polygons); // Register ProjectParams structure value_object("ProjectParams") @@ -403,11 +459,6 @@ EMSCRIPTEN_BINDINGS(uvula) function("unwrap", &unwrapTyped); function("project", &projectWithParams); - // Version information - value_object("uvula_info_t") - .field("uvula_version", &uvula_info_t::uvula_version) - .field("git_hash", &uvula_info_t::git_hash); - function("uvula_info", &get_uvula_info); // Utility classes for direct access if needed From 02cb395efd74794cc020d9c0970632ca83c1bbc5 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Tue, 16 Dec 2025 11:29:12 +0100 Subject: [PATCH 05/13] Expand GitHub Actions path triggers in `package.yml` to include `UvulaJS` directory NP-1250 --- .github/workflows/package.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 86326e2..6275e86 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -11,6 +11,7 @@ on: - 'conandata.yml' - 'CMakeLists.txt' - '.github/workflows/package.yml' + - 'UvulaJS/**' branches: - main - master From 328888207af933fbd593b19279dfacb0a81335dd Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Tue, 16 Dec 2025 14:19:15 +0100 Subject: [PATCH 06/13] Refactor `UvulaJS` bindings to enhance TypeScript compatibility - Introduced `PolygonArray` class to wrap arrays for improved TypeScript typing. - Replaced `std::vector` with `Float32Array` and `PolygonArray` in key data structures. - Enhanced `unwrap` and `project` methods to utilize TypeScript-friendly return types. - Updated Emscripten bindings to register `PolygonArray` and support structured data types. NP-1250 --- UvulaJS/UvulaJS.cpp | 71 +++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 21 deletions(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index 53832c6..0818fd6 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -19,10 +19,35 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(Float32Array); EMSCRIPTEN_DECLARE_VAL_TYPE(Uint32Array); EMSCRIPTEN_DECLARE_VAL_TYPE(Int32Array); +// Wrapper for array of Float32Array to provide proper TypeScript typing +class PolygonArray { +public: + emscripten::val polygons; + + PolygonArray() : polygons(emscripten::val::array()) {} + explicit PolygonArray(emscripten::val arr) : polygons(arr) {} + + void push(const Float32Array& polygon) { + polygons.call("push", polygon); + } + + emscripten::val get(int index) const { + return polygons[index]; + } + + int size() const { + return polygons["length"].as(); + } + + emscripten::val toArray() const { + return polygons; + } +}; + // Return type for unwrap function struct UnwrapResult { - std::vector uvCoordinates; + Float32Array uvCoordinates = Float32Array{emscripten::val::array()}; uint32_t textureWidth; uint32_t textureHeight; }; @@ -30,7 +55,7 @@ struct UnwrapResult // Return type for project function (array of polygons as flat arrays) struct ProjectResult { - std::vector> polygons; + PolygonArray polygons; }; // Parameter structure for project function @@ -98,16 +123,15 @@ UnwrapResult unwrapTyped(const Float32Array& vertices_js, const Uint32Array& ind } // Convert result to structured return type - std::vector uv_array; - uv_array.reserve(uv_coords.size() * 2); + emscripten::val uv_array = emscripten::val::array(); - for (const auto& coord : uv_coords) { - uv_array.push_back(coord.x); - uv_array.push_back(coord.y); + for (size_t i = 0; i < uv_coords.size(); ++i) { + uv_array.set(i * 2, uv_coords[i].x); + uv_array.set(i * 2 + 1, uv_coords[i].y); } return UnwrapResult{ - .uvCoordinates = uv_array, + .uvCoordinates = Float32Array{uv_array}, .textureWidth = texture_width, .textureHeight = texture_height }; @@ -212,17 +236,17 @@ ProjectResult projectTyped( ); // Convert result to structured return type - std::vector> result_polygons; - result_polygons.reserve(result.size()); - - for (const auto& polygon : result) { - std::vector polygon_flat; - polygon_flat.reserve(polygon.size() * 2); - for (const auto& point : polygon) { - polygon_flat.push_back(point.x); - polygon_flat.push_back(point.y); + PolygonArray result_polygons; + + for (size_t i = 0; i < result.size(); ++i) { + emscripten::val polygon_array = emscripten::val::array(); + const auto& polygon = result[i]; + for (size_t j = 0; j < polygon.size(); ++j) { + polygon_array.set(j * 2, polygon[j].x); + polygon_array.set(j * 2 + 1, polygon[j].y); } - result_polygons.push_back(std::move(polygon_flat)); + // Add Float32Array to the result array + result_polygons.push(Float32Array{polygon_array}); } return ProjectResult{.polygons = result_polygons}; @@ -421,15 +445,20 @@ emscripten::val jsProject( EMSCRIPTEN_BINDINGS(uvula) { - // Register typed array types - register_vector("Float32Array"); - register_vector>("Float32ArrayArray"); // Register TypeScript-style typed arrays emscripten::register_type("Float32Array"); emscripten::register_type("Uint32Array"); emscripten::register_type("Int32Array"); + // Register PolygonArray class for proper TypeScript typing + class_("PolygonArray") + .constructor<>() + .function("push", &PolygonArray::push) + .function("get", &PolygonArray::get) + .function("size", &PolygonArray::size) + .function("toArray", &PolygonArray::toArray); + // Register structured return types value_object("UnwrapResult") .field("uvCoordinates", &UnwrapResult::uvCoordinates) From 2422f590bd0cf8d8d45b44b25b3d0dbcc08e8768 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Tue, 16 Dec 2025 15:53:51 +0100 Subject: [PATCH 07/13] Add Emscripten-specific threading configuration and refactor memory allocation - Disabled multithreading for Emscripten/WASM builds. - Simplified `TaskGroup` allocation logic. NP-1250 --- src/xatlas.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/xatlas.cpp b/src/xatlas.cpp index 8e03b55..9fd6afc 100644 --- a/src/xatlas.cpp +++ b/src/xatlas.cpp @@ -62,8 +62,12 @@ Copyright (c) 2012 Brandon Pelfrey #endif #ifndef XA_MULTITHREADED +#ifdef __EMSCRIPTEN__ +#define XA_MULTITHREADED 0 // Disable threading for Emscripten/WASM +#else #define XA_MULTITHREADED 1 #endif +#endif #define XA_STR(x) #x #define XA_XSTR(x) XA_STR(x) @@ -1888,7 +1892,7 @@ class TaskScheduler TaskGroupHandle createTaskGroup(void* userData = nullptr, uint32_t reserveSize = 0) { - TaskGroup* group = XA_NEW(MemTag::Default, TaskGroup); + TaskGroup* group = XA_NEW(TaskGroup); group->queue.reserve(reserveSize); group->userData = userData; m_groups.push_back(group); From d46eaba47a5eb7cc6c47ef9b904555d1efd1cb93 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Sun, 11 Jan 2026 19:16:26 +0100 Subject: [PATCH 08/13] Simplify bindings NP-1237 --- UvulaJS/UvulaJS.cpp | 321 ++++---------------------------------------- 1 file changed, 29 insertions(+), 292 deletions(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index 0818fd6..1a06d00 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -18,31 +18,8 @@ using namespace emscripten; EMSCRIPTEN_DECLARE_VAL_TYPE(Float32Array); EMSCRIPTEN_DECLARE_VAL_TYPE(Uint32Array); EMSCRIPTEN_DECLARE_VAL_TYPE(Int32Array); - -// Wrapper for array of Float32Array to provide proper TypeScript typing -class PolygonArray { -public: - emscripten::val polygons; - - PolygonArray() : polygons(emscripten::val::array()) {} - explicit PolygonArray(emscripten::val arr) : polygons(arr) {} - - void push(const Float32Array& polygon) { - polygons.call("push", polygon); - } - - emscripten::val get(int index) const { - return polygons[index]; - } - - int size() const { - return polygons["length"].as(); - } - - emscripten::val toArray() const { - return polygons; - } -}; +EMSCRIPTEN_DECLARE_VAL_TYPE(UInt32Array); +EMSCRIPTEN_DECLARE_VAL_TYPE(PolygonArray); // Return type for unwrap function struct UnwrapResult @@ -52,37 +29,13 @@ struct UnwrapResult uint32_t textureHeight; }; -// Return type for project function (array of polygons as flat arrays) -struct ProjectResult -{ - PolygonArray polygons; -}; - -// Parameter structure for project function -struct ProjectParams -{ - Float32Array strokePolygon = Float32Array{emscripten::val::array()}; // float[] - Float32Array meshVertices = Float32Array{emscripten::val::array()}; // float[] - Uint32Array meshIndices = Uint32Array{emscripten::val::array()}; // uint32_t[] - Float32Array meshUV = Float32Array{emscripten::val::array()}; // float[] - Int32Array meshFacesConnectivity = Int32Array{emscripten::val::array()}; // int32_t[] - uint32_t textureWidth = 0; - uint32_t textureHeight = 0; - Float32Array cameraProjectionMatrix = Float32Array{emscripten::val::array()}; // float[16] - 4x4 matrix - bool isCameraPerspective = false; - uint32_t viewportWidth = 0; - uint32_t viewportHeight = 0; - Float32Array cameraNormal = Float32Array{emscripten::val::array()}; // float[3] - normal vector - uint32_t faceId = 0; -}; - std::string get_uvula_info() { return { UVULA_VERSION }; } // Typed wrapper functions for better TypeScript generation -UnwrapResult unwrapTyped(const Float32Array& vertices_js, const Uint32Array& indices_js) +UnwrapResult unwrap(const Float32Array& vertices_js, const Uint32Array& indices_js) { // Convert JavaScript arrays to C++ vectors std::vector vertex_points; @@ -103,7 +56,8 @@ UnwrapResult unwrapTyped(const Float32Array& vertices_js, const Uint32Array& ind // Convert indices (expecting flat array of [i1, i2, i3, i4, i5, i6, ...]) face_indices.reserve(indices_length / 3); - for (unsigned i = 0; i < indices_length; i += 3) { + for (unsigned i = 0; i < indices_length; i += 3) + { uint32_t i1 = indices_js[i].as(); uint32_t i2 = indices_js[i + 1].as(); uint32_t i3 = indices_js[i + 2].as(); @@ -118,14 +72,16 @@ UnwrapResult unwrapTyped(const Float32Array& vertices_js, const Uint32Array& ind // Perform unwrapping bool success = smartUnwrap(vertex_points, face_indices, uv_coords, texture_width, texture_height); - if (!success) { + if (!success) + { throw std::runtime_error("Couldn't unwrap UVs!"); } // Convert result to structured return type emscripten::val uv_array = emscripten::val::array(); - for (size_t i = 0; i < uv_coords.size(); ++i) { + for (size_t i = 0; i < uv_coords.size(); ++i) + { uv_array.set(i * 2, uv_coords[i].x); uv_array.set(i * 2 + 1, uv_coords[i].y); } @@ -137,7 +93,7 @@ UnwrapResult unwrapTyped(const Float32Array& vertices_js, const Uint32Array& ind }; } -ProjectResult projectTyped( +PolygonArray project( const Float32Array& stroke_polygon_js, const Float32Array& mesh_vertices_js, const Uint32Array& mesh_indices_js, @@ -149,8 +105,9 @@ ProjectResult projectTyped( bool is_camera_perspective, uint32_t viewport_width, uint32_t viewport_height, - const Float32Array& camera_normal_js, - uint32_t face_id) + const Vector3F& camera_normal, + uint32_t face_id +) { // Convert stroke polygon (flat array of [x1, y1, x2, y2, ...]) std::vector stroke_points; @@ -212,12 +169,6 @@ ProjectResult projectTyped( } Matrix44F projection_matrix(matrix_data); - // Convert camera normal (3-element array) - float normal_x = camera_normal_js[0].as(); - float normal_y = camera_normal_js[1].as(); - float normal_z = camera_normal_js[2].as(); - Vector3F normal(normal_x, normal_y, normal_z); - // Call the projection function std::vector result = doProject( stroke_points, @@ -231,233 +182,35 @@ ProjectResult projectTyped( is_camera_perspective, viewport_width, viewport_height, - normal, + camera_normal, face_id ); // Convert result to structured return type - PolygonArray result_polygons; + emscripten::val result_polygons = emscripten::val::array(); - for (size_t i = 0; i < result.size(); ++i) { + for (size_t i = 0; i < result.size(); ++i) + { emscripten::val polygon_array = emscripten::val::array(); const auto& polygon = result[i]; - for (size_t j = 0; j < polygon.size(); ++j) { - polygon_array.set(j * 2, polygon[j].x); - polygon_array.set(j * 2 + 1, polygon[j].y); + for (size_t j = 0; j < polygon.size(); ++j) + { + polygon_array.set(j, polygon[j]); } - // Add Float32Array to the result array - result_polygons.push(Float32Array{polygon_array}); - } - - return ProjectResult{.polygons = result_polygons}; -} - -// Structured wrapper for project function using ProjectParams -ProjectResult projectWithParams(const ProjectParams& params) -{ - return projectTyped( - params.strokePolygon, - params.meshVertices, - params.meshIndices, - params.meshUV, - params.meshFacesConnectivity, - params.textureWidth, - params.textureHeight, - params.cameraProjectionMatrix, - params.isCameraPerspective, - params.viewportWidth, - params.viewportHeight, - params.cameraNormal, - params.faceId - ); -} - -// Wrapper for the unwrap function that works with JavaScript arrays -emscripten::val jsUnwrap(const emscripten::val& vertices_js, const emscripten::val& indices_js) -{ - // Convert JavaScript arrays to C++ vectors - std::vector vertices; - std::vector indices; - - // Get array lengths - unsigned vertices_length = vertices_js["length"].as(); - unsigned indices_length = indices_js["length"].as(); - - // Convert vertices (expecting flat array of [x1, y1, z1, x2, y2, z2, ...]) - vertices.reserve(vertices_length / 3); - for (unsigned i = 0; i < vertices_length; i += 3) { - float x = vertices_js[i].as(); - float y = vertices_js[i + 1].as(); - float z = vertices_js[i + 2].as(); - vertices.emplace_back(x, y, z); - } - - // Convert indices (expecting flat array of [i1, i2, i3, i4, i5, i6, ...]) - indices.reserve(indices_length / 3); - for (unsigned i = 0; i < indices_length; i += 3) { - uint32_t i1 = indices_js[i].as(); - uint32_t i2 = indices_js[i + 1].as(); - uint32_t i3 = indices_js[i + 2].as(); - indices.push_back({i1, i2, i3}); - } - - // Prepare output - std::vector uv_coords(vertices.size(), {0.0f, 0.0f}); - uint32_t texture_width; - uint32_t texture_height; - - // Perform unwrapping - bool success = smartUnwrap(vertices, indices, uv_coords, texture_width, texture_height); - - if (!success) { - throw std::runtime_error("Couldn't unwrap UVs!"); - } - - // Convert result to JavaScript object - emscripten::val result = emscripten::val::object(); - emscripten::val uv_array = emscripten::val::array(); - - for (size_t i = 0; i < uv_coords.size(); ++i) { - uv_array.set(i * 2, uv_coords[i].x); - uv_array.set(i * 2 + 1, uv_coords[i].y); - } - - result.set("uvCoordinates", uv_array); - result.set("textureWidth", texture_width); - result.set("textureHeight", texture_height); - - return result; -} - -// Wrapper for the project function that works with JavaScript arrays -emscripten::val jsProject( - const emscripten::val& stroke_polygon_js, - const emscripten::val& mesh_vertices_js, - const emscripten::val& mesh_indices_js, - const emscripten::val& mesh_uv_js, - const emscripten::val& mesh_faces_connectivity_js, - uint32_t texture_width, - uint32_t texture_height, - const emscripten::val& camera_projection_matrix_js, - bool is_camera_perspective, - uint32_t viewport_width, - uint32_t viewport_height, - const emscripten::val& camera_normal_js, - uint32_t face_id) -{ - // Convert stroke polygon (flat array of [x1, y1, x2, y2, ...]) - std::vector stroke_polygon; - unsigned stroke_length = stroke_polygon_js["length"].as(); - stroke_polygon.reserve(stroke_length / 2); - for (unsigned i = 0; i < stroke_length; i += 2) { - float x = stroke_polygon_js[i].as(); - float y = stroke_polygon_js[i + 1].as(); - stroke_polygon.push_back({x, y}); - } - // Convert mesh vertices (flat array of [x1, y1, z1, x2, y2, z2, ...]) - std::vector mesh_vertices; - unsigned vertices_length = mesh_vertices_js["length"].as(); - mesh_vertices.reserve(vertices_length / 3); - for (unsigned i = 0; i < vertices_length; i += 3) { - float x = mesh_vertices_js[i].as(); - float y = mesh_vertices_js[i + 1].as(); - float z = mesh_vertices_js[i + 2].as(); - mesh_vertices.emplace_back(x, y, z); + result_polygons.set(i, polygon_array); } - // Convert mesh indices (flat array of [i1, i2, i3, i4, i5, i6, ...]) - std::vector mesh_indices; - unsigned indices_length = mesh_indices_js["length"].as(); - mesh_indices.reserve(indices_length / 3); - for (unsigned i = 0; i < indices_length; i += 3) { - uint32_t i1 = mesh_indices_js[i].as(); - uint32_t i2 = mesh_indices_js[i + 1].as(); - uint32_t i3 = mesh_indices_js[i + 2].as(); - mesh_indices.push_back({i1, i2, i3}); - } - - // Convert mesh UV coordinates (flat array of [u1, v1, u2, v2, ...]) - std::vector mesh_uv; - unsigned uv_length = mesh_uv_js["length"].as(); - mesh_uv.reserve(uv_length / 2); - for (unsigned i = 0; i < uv_length; i += 2) { - float u = mesh_uv_js[i].as(); - float v = mesh_uv_js[i + 1].as(); - mesh_uv.push_back({u, v}); - } - - // Convert mesh faces connectivity (flat array of [f1, f2, f3, f4, f5, f6, ...]) - std::vector mesh_faces_connectivity; - unsigned connectivity_length = mesh_faces_connectivity_js["length"].as(); - mesh_faces_connectivity.reserve(connectivity_length / 3); - for (unsigned i = 0; i < connectivity_length; i += 3) { - int32_t i1 = mesh_faces_connectivity_js[i].as(); - int32_t i2 = mesh_faces_connectivity_js[i + 1].as(); - int32_t i3 = mesh_faces_connectivity_js[i + 2].as(); - mesh_faces_connectivity.push_back({i1, i2, i3}); - } - - // Convert camera projection matrix (4x4 matrix as flat array) - float matrix_data[4][4]; - for (int i = 0; i < 16; ++i) { - matrix_data[i / 4][i % 4] = camera_projection_matrix_js[i].as(); - } - Matrix44F camera_projection_matrix(matrix_data); - - // Convert camera normal (3-element array) - float normal_x = camera_normal_js[0].as(); - float normal_y = camera_normal_js[1].as(); - float normal_z = camera_normal_js[2].as(); - Vector3F camera_normal(normal_x, normal_y, normal_z); - - // Call the projection function - std::vector result = doProject( - stroke_polygon, - mesh_vertices, - mesh_indices, - mesh_uv, - mesh_faces_connectivity, - texture_width, - texture_height, - camera_projection_matrix, - is_camera_perspective, - viewport_width, - viewport_height, - camera_normal, - face_id - ); - - // Convert result to JavaScript array of arrays - emscripten::val js_result = emscripten::val::array(); - for (size_t i = 0; i < result.size(); ++i) { - emscripten::val polygon_array = emscripten::val::array(); - const Polygon& polygon = result[i]; - for (size_t j = 0; j < polygon.size(); ++j) { - polygon_array.set(j * 2, polygon[j].x); - polygon_array.set(j * 2 + 1, polygon[j].y); - } - js_result.set(i, polygon_array); - } - - return js_result; + return PolygonArray{ result_polygons }; } EMSCRIPTEN_BINDINGS(uvula) { - // Register TypeScript-style typed arrays emscripten::register_type("Float32Array"); emscripten::register_type("Uint32Array"); emscripten::register_type("Int32Array"); - - // Register PolygonArray class for proper TypeScript typing - class_("PolygonArray") - .constructor<>() - .function("push", &PolygonArray::push) - .function("get", &PolygonArray::get) - .function("size", &PolygonArray::size) - .function("toArray", &PolygonArray::toArray); + emscripten::register_type("Point2F[][]"); // Register structured return types value_object("UnwrapResult") @@ -465,28 +218,9 @@ EMSCRIPTEN_BINDINGS(uvula) .field("textureWidth", &UnwrapResult::textureWidth) .field("textureHeight", &UnwrapResult::textureHeight); - value_object("ProjectResult") - .field("polygons", &ProjectResult::polygons); - - // Register ProjectParams structure - value_object("ProjectParams") - .field("strokePolygon", &ProjectParams::strokePolygon) - .field("meshVertices", &ProjectParams::meshVertices) - .field("meshIndices", &ProjectParams::meshIndices) - .field("meshUV", &ProjectParams::meshUV) - .field("meshFacesConnectivity", &ProjectParams::meshFacesConnectivity) - .field("textureWidth", &ProjectParams::textureWidth) - .field("textureHeight", &ProjectParams::textureHeight) - .field("cameraProjectionMatrix", &ProjectParams::cameraProjectionMatrix) - .field("isCameraPerspective", &ProjectParams::isCameraPerspective) - .field("viewportWidth", &ProjectParams::viewportWidth) - .field("viewportHeight", &ProjectParams::viewportHeight) - .field("cameraNormal", &ProjectParams::cameraNormal) - .field("faceId", &ProjectParams::faceId); - // Main typed functions with proper TypeScript signatures - function("unwrap", &unwrapTyped); - function("project", &projectWithParams); + function("unwrap", &unwrap); + function("project", &project); function("uvula_info", &get_uvula_info); @@ -503,7 +237,10 @@ EMSCRIPTEN_BINDINGS(uvula) .function("z", &Point3F::z); class_("Vector3F") - .constructor(); + .constructor() + .function("x", &Vector3F::x) + .function("y", &Vector3F::y) + .function("z", &Vector3F::z); value_object("Face") .field("i1", &Face::i1) From 8c483b02c38104dca4f797c7b3da04d9d77193ff Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Mon, 12 Jan 2026 13:19:24 +0100 Subject: [PATCH 09/13] Transpose camera matrix --- UvulaJS/UvulaJS.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index 1a06d00..51f997f 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -165,7 +165,7 @@ PolygonArray project( // Convert camera projection matrix (4x4 matrix as flat array) float matrix_data[4][4]; for (int i = 0; i < 16; ++i) { - matrix_data[i / 4][i % 4] = camera_projection_matrix_js[i].as(); + matrix_data[i % 4][i / 4] = camera_projection_matrix_js[i].as(); } Matrix44F projection_matrix(matrix_data); From 202d95cdba3e8b714bd95b78277cf5f5e4a99783 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 14 Jan 2026 13:55:49 +0100 Subject: [PATCH 10/13] Construct `Float32Array` to return uv coords NP-1250 --- UvulaJS/UvulaJS.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index 51f997f..89662cf 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -78,8 +78,7 @@ UnwrapResult unwrap(const Float32Array& vertices_js, const Uint32Array& indices_ } // Convert result to structured return type - emscripten::val uv_array = emscripten::val::array(); - + emscripten::val uv_array = emscripten::val::global("Float32Array").new_(uv_coords.size() * 2); for (size_t i = 0; i < uv_coords.size(); ++i) { uv_array.set(i * 2, uv_coords[i].x); From 2516aadf5295ba30c77542bd36298760f4dc8273 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Wed, 14 Jan 2026 22:35:04 +0100 Subject: [PATCH 11/13] Move vertices in `Geometry` container NP-1250 --- UvulaJS/UvulaJS.cpp | 119 +++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index 89662cf..e3a4bea 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -29,6 +29,57 @@ struct UnwrapResult uint32_t textureHeight; }; +struct Geometry +{ + std::vector vertices; + std::vector indices; + std::vector uvs; + std::vector connectivity; + + Geometry(const Float32Array& vertices_js, const Uint32Array& indices_js, const Float32Array& uvs_js, const Int32Array& connectivity_js) + { + // Convert vertices + auto vertices_length = vertices_js["length"].as(); + vertices.reserve(vertices_length / 3); + for (int i = 0; i < vertices_length; i += 3) { + auto x = vertices_js[i].as(); + auto y = vertices_js[i + 1].as(); + auto z = vertices_js[i + 2].as(); + vertices.emplace_back(x, y, z); + } + + // Convert indices + auto indices_length = indices_js["length"].as(); + indices.reserve(indices_length / 3); + for (int i = 0; i < indices_length; i += 3) + { + auto i1 = indices_js[i].as(); + auto i2 = indices_js[i + 1].as(); + auto i3 = indices_js[i + 2].as(); + indices.push_back({i1, i2, i3}); + } + + // Convert UVs + auto uvs_length = uvs_js["length"].as(); + uvs.reserve(uvs_length / 2); + for (int i = 0; i < uvs_length; i += 2) { + auto u = uvs_js[i].as(); + auto v = uvs_js[i + 1].as(); + uvs.emplace_back(u, v); + } + + // Convert connectivity + auto connectivity_length = connectivity_js["length"].as(); + connectivity.reserve(connectivity_length / 3); + for (int i = 0; i < connectivity_length; i += 3) { + auto i1 = connectivity_js[i].as(); + auto i2 = connectivity_js[i + 1].as(); + auto i3 = connectivity_js[i + 2].as(); + connectivity.push_back({i1, i2, i3}); + } + } +}; + std::string get_uvula_info() { return { UVULA_VERSION }; @@ -94,10 +145,7 @@ UnwrapResult unwrap(const Float32Array& vertices_js, const Uint32Array& indices_ PolygonArray project( const Float32Array& stroke_polygon_js, - const Float32Array& mesh_vertices_js, - const Uint32Array& mesh_indices_js, - const Float32Array& mesh_uv_js, - const Int32Array& mesh_faces_connectivity_js, + Geometry& geometry, uint32_t texture_width, uint32_t texture_height, const Float32Array& camera_projection_matrix_js, @@ -108,59 +156,15 @@ PolygonArray project( uint32_t face_id ) { - // Convert stroke polygon (flat array of [x1, y1, x2, y2, ...]) std::vector stroke_points; - unsigned stroke_length = stroke_polygon_js["length"].as(); + auto stroke_length = stroke_polygon_js["length"].as(); stroke_points.reserve(stroke_length / 2); - for (unsigned i = 0; i < stroke_length; i += 2) { - float x = stroke_polygon_js[i].as(); - float y = stroke_polygon_js[i + 1].as(); + for (int i = 0; i < stroke_length; i += 2) { + auto x = stroke_polygon_js[i].as(); + auto y = stroke_polygon_js[i + 1].as(); stroke_points.push_back({x, y}); } - // Convert mesh vertices (flat array of [x1, y1, z1, x2, y2, z2, ...]) - std::vector vertex_points; - unsigned vertices_length = mesh_vertices_js["length"].as(); - vertex_points.reserve(vertices_length / 3); - for (unsigned i = 0; i < vertices_length; i += 3) { - float x = mesh_vertices_js[i].as(); - float y = mesh_vertices_js[i + 1].as(); - float z = mesh_vertices_js[i + 2].as(); - vertex_points.emplace_back(x, y, z); - } - - // Convert mesh indices (flat array of [i1, i2, i3, i4, i5, i6, ...]) - std::vector face_indices; - unsigned indices_length = mesh_indices_js["length"].as(); - face_indices.reserve(indices_length / 3); - for (unsigned i = 0; i < indices_length; i += 3) { - uint32_t i1 = mesh_indices_js[i].as(); - uint32_t i2 = mesh_indices_js[i + 1].as(); - uint32_t i3 = mesh_indices_js[i + 2].as(); - face_indices.push_back({i1, i2, i3}); - } - - // Convert mesh UV coordinates (flat array of [u1, v1, u2, v2, ...]) - std::vector uv_points; - unsigned uv_length = mesh_uv_js["length"].as(); - uv_points.reserve(uv_length / 2); - for (unsigned i = 0; i < uv_length; i += 2) { - float u = mesh_uv_js[i].as(); - float v = mesh_uv_js[i + 1].as(); - uv_points.push_back({u, v}); - } - - // Convert mesh faces connectivity (flat array of [f1, f2, f3, f4, f5, f6, ...]) - std::vector connectivity; - unsigned connectivity_length = mesh_faces_connectivity_js["length"].as(); - connectivity.reserve(connectivity_length / 3); - for (unsigned i = 0; i < connectivity_length; i += 3) { - int32_t i1 = mesh_faces_connectivity_js[i].as(); - int32_t i2 = mesh_faces_connectivity_js[i + 1].as(); - int32_t i3 = mesh_faces_connectivity_js[i + 2].as(); - connectivity.push_back({i1, i2, i3}); - } - // Convert camera projection matrix (4x4 matrix as flat array) float matrix_data[4][4]; for (int i = 0; i < 16; ++i) { @@ -171,10 +175,10 @@ PolygonArray project( // Call the projection function std::vector result = doProject( stroke_points, - vertex_points, - face_indices, - uv_points, - connectivity, + geometry.vertices, + geometry.indices, + geometry.uvs, + geometry.connectivity, texture_width, texture_height, projection_matrix, @@ -223,6 +227,9 @@ EMSCRIPTEN_BINDINGS(uvula) function("uvula_info", &get_uvula_info); + class_("Geometry") + .constructor(); + // Utility classes for direct access if needed class_("Point2F") .constructor<>() From 6332d8b65a87c9b926483dacfd2e646433c2a916 Mon Sep 17 00:00:00 2001 From: "c.lamboo" Date: Thu, 15 Jan 2026 16:49:26 +0100 Subject: [PATCH 12/13] Fix js bindings so we can provide a `const Geometry&` in `project` function NP-1250 --- UvulaJS/UvulaJS.cpp | 2 +- include/project.h | 8 ++++---- pyUvula/pyUvula.cpp | 10 +++++----- src/project.cpp | 14 +++++++------- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/UvulaJS/UvulaJS.cpp b/UvulaJS/UvulaJS.cpp index e3a4bea..f2f557a 100644 --- a/UvulaJS/UvulaJS.cpp +++ b/UvulaJS/UvulaJS.cpp @@ -145,7 +145,7 @@ UnwrapResult unwrap(const Float32Array& vertices_js, const Uint32Array& indices_ PolygonArray project( const Float32Array& stroke_polygon_js, - Geometry& geometry, + const Geometry& geometry, uint32_t texture_width, uint32_t texture_height, const Float32Array& camera_projection_matrix_js, diff --git a/include/project.h b/include/project.h index aca2c9a..6a936bb 100644 --- a/include/project.h +++ b/include/project.h @@ -34,10 +34,10 @@ using Polygon = std::vector; */ std::vector doProject( const std::span& stroke_polygon, - const std::span& mesh_vertices, - const std::span& mesh_indices, - const std::span& mesh_uv, - const std::span& mesh_faces_connectivity, + const std::span& mesh_vertices, + const std::span& mesh_indices, + const std::span& mesh_uv, + const std::span& mesh_faces_connectivity, const uint32_t texture_width, const uint32_t texture_height, const Matrix44F& camera_projection_matrix, diff --git a/pyUvula/pyUvula.cpp b/pyUvula/pyUvula.cpp index ab01f2f..39674b9 100644 --- a/pyUvula/pyUvula.cpp +++ b/pyUvula/pyUvula.cpp @@ -65,19 +65,19 @@ py::list pyProject( const uint32_t face_id) { pybind11::buffer_info stroke_polygon_buffer = stroke_polygon_array.request(); - const std::span stroke_polygon = std::span(static_cast(stroke_polygon_buffer.ptr), stroke_polygon_buffer.shape[0]); + const std::span stroke_polygon = std::span(static_cast(stroke_polygon_buffer.ptr), stroke_polygon_buffer.shape[0]); pybind11::buffer_info mesh_vertices_buffer = mesh_vertices_array.request(); - const std::span mesh_vertices = std::span(static_cast(mesh_vertices_buffer.ptr), mesh_vertices_buffer.shape[0]); + const std::span mesh_vertices = std::span(static_cast(mesh_vertices_buffer.ptr), mesh_vertices_buffer.shape[0]); pybind11::buffer_info mesh_indices_buffer = mesh_indices_array.request(); - const std::span mesh_indices = std::span(static_cast(mesh_indices_buffer.ptr), mesh_indices_buffer.shape[0]); + const std::span mesh_indices = std::span(static_cast(mesh_indices_buffer.ptr), mesh_indices_buffer.shape[0]); pybind11::buffer_info mesh_uv_buffer = mesh_uv_array.request(); - const std::span mesh_uv = std::span(static_cast(mesh_uv_buffer.ptr), mesh_uv_buffer.shape[0]); + const std::span mesh_uv = std::span(static_cast(mesh_uv_buffer.ptr), mesh_uv_buffer.shape[0]); pybind11::buffer_info mesh_faces_connectivity_buffer = mesh_faces_connectivity_array.request(); - const std::span mesh_faces_connectivity = std::span(static_cast(mesh_faces_connectivity_buffer.ptr), mesh_faces_connectivity_buffer.shape[0]); + const std::span mesh_faces_connectivity = std::span(static_cast(mesh_faces_connectivity_buffer.ptr), mesh_faces_connectivity_buffer.shape[0]); const pybind11::buffer_info camera_projection_matrix_buf = camera_projection_matrix_array.request(); const Matrix44F camera_projection_matrix(*static_cast(camera_projection_matrix_buf.ptr)); diff --git a/src/project.cpp b/src/project.cpp index 5783b60..195b0dc 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -18,17 +18,17 @@ static constexpr float CLIPPER_PRECISION = 1000.0; -Face getFace(const std::span& mesh_indices, const uint32_t face_index) +Face getFace(const std::span& mesh_indices, const uint32_t face_index) { return mesh_indices.empty() ? Face{ face_index * 3, face_index * 3 + 1, face_index * 3 + 2 } : mesh_indices[face_index]; } -Triangle3F getFaceTriangle(const std::span& mesh_vertices, const Face& face) +Triangle3F getFaceTriangle(const std::span& mesh_vertices, const Face& face) { return Triangle3F(mesh_vertices[face.i1], mesh_vertices[face.i2], mesh_vertices[face.i3]); } -Triangle2F getFaceUv(const std::span& mesh_uv, const Face& face) +Triangle2F getFaceUv(const std::span& mesh_uv, const Face& face) { return Triangle2F{ mesh_uv[face.i1], mesh_uv[face.i2], mesh_uv[face.i3] }; } @@ -157,10 +157,10 @@ std::vector toPolygons(const ClipperLib::Paths& paths) std::vector doProject( const std::span& stroke_polygon, - const std::span& mesh_vertices, - const std::span& mesh_indices, - const std::span& mesh_uv, - const std::span& mesh_faces_connectivity, + const std::span& mesh_vertices, + const std::span& mesh_indices, + const std::span& mesh_uv, + const std::span& mesh_faces_connectivity, const uint32_t texture_width, const uint32_t texture_height, const Matrix44F& camera_projection_matrix, From 35aed92a69d069595e8547b446849b2e216617d7 Mon Sep 17 00:00:00 2001 From: Erwan MATHIEU Date: Fri, 16 Jan 2026 07:52:18 +0100 Subject: [PATCH 13/13] Fix Python binding NP-1250 --- pyUvula/pyUvula.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyUvula/pyUvula.cpp b/pyUvula/pyUvula.cpp index 39674b9..07c6dcd 100644 --- a/pyUvula/pyUvula.cpp +++ b/pyUvula/pyUvula.cpp @@ -65,7 +65,7 @@ py::list pyProject( const uint32_t face_id) { pybind11::buffer_info stroke_polygon_buffer = stroke_polygon_array.request(); - const std::span stroke_polygon = std::span(static_cast(stroke_polygon_buffer.ptr), stroke_polygon_buffer.shape[0]); + const std::span stroke_polygon = std::span(static_cast(stroke_polygon_buffer.ptr), stroke_polygon_buffer.shape[0]); pybind11::buffer_info mesh_vertices_buffer = mesh_vertices_array.request(); const std::span mesh_vertices = std::span(static_cast(mesh_vertices_buffer.ptr), mesh_vertices_buffer.shape[0]); @@ -80,7 +80,7 @@ py::list pyProject( const std::span mesh_faces_connectivity = std::span(static_cast(mesh_faces_connectivity_buffer.ptr), mesh_faces_connectivity_buffer.shape[0]); const pybind11::buffer_info camera_projection_matrix_buf = camera_projection_matrix_array.request(); - const Matrix44F camera_projection_matrix(*static_cast(camera_projection_matrix_buf.ptr)); + const Matrix44F camera_projection_matrix(*static_cast(camera_projection_matrix_buf.ptr)); const pybind11::buffer_info camera_normal_buf = camera_normal_array.request(); const float* camera_normal_ptr = static_cast(camera_normal_buf.ptr);