Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update JS bindings with CrossSection + reorganize #440

Merged
merged 41 commits into from
Jun 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
69ebacd
Update JS bindings with CrossSection + reorganize
geoffder May 25, 2023
fdd6f56
ignore no-arg call of Module
geoffder May 25, 2023
47c674e
roughly group functions names by purpose
geoffder May 25, 2023
356c9bf
clang-format pass over js files
geoffder May 26, 2023
7c25583
Move debug setup below memory registry indirection
geoffder May 26, 2023
8a9b062
Re-reformat
geoffder May 26, 2023
623306b
Add back undefined check
geoffder May 26, 2023
39ed5d7
remove spurious newline
geoffder May 26, 2023
b06c89e
Move reserveIDs under Manifold
geoffder May 26, 2023
c6b89f5
Add center param to extrude
geoffder May 26, 2023
2908a20
Add missing this argument back to _Extrude
geoffder May 26, 2023
8423e6e
Fix wrong timeout key
geoffder May 27, 2023
2df7829
rebuild on interface modification (ensure copying)
geoffder May 27, 2023
bbd8ee1
Remove redundant checks in scale methods
geoffder May 27, 2023
618836c
Undo scale change (vararg2vec not redundant)
geoffder May 27, 2023
4301220
Fix bindings.cpp properties (js deps)
geoffder May 27, 2023
d8a9f18
Move reserveIDs into manifold intf as static
geoffder May 27, 2023
01820d7
Simplify memory registry closure
geoffder May 27, 2023
98408f3
Cleanup examples with local method opens
geoffder May 27, 2023
5690a3a
Update Manifold interface (TODO add CrossSection)
geoffder May 27, 2023
78d7509
Copy js interface files on mod without recompiling
geoffder May 27, 2023
0f3e52a
Add note to READMEs about Firefox fix
geoffder May 27, 2023
233e81f
Add CrossSection interface and associated types
geoffder May 27, 2023
b30cc25
Revert Polygons and fix signatures
geoffder May 29, 2023
55320c7
Fix instanceof for Manifold and CrossSection
geoffder May 29, 2023
f3dc805
Fix signatures of extrude and revolve
geoffder May 29, 2023
4d43bb6
Extend show and only to take CrossSections
geoffder May 29, 2023
7aba193
Copy ManifoldToplevel into editor.js (necessary?)
geoffder May 29, 2023
817544a
Fix enums in CrossSection constr and offset
geoffder May 29, 2023
4871d54
Doc comment fixes
geoffder May 29, 2023
8a389ca
allow scalar input to extrude's scaleTop parameter
geoffder May 30, 2023
2fa3f44
Use CrossSection a bit more in examples
geoffder May 30, 2023
97e4381
Remove std::string from enum handling
geoffder May 30, 2023
f0a518e
Undo flubbed bracelet change, update intro test
geoffder May 30, 2023
2dc2b21
organizational comments
geoffder May 30, 2023
11bc0c9
Revert show/only changes
geoffder May 30, 2023
5b05c42
Organizational comments
geoffder May 30, 2023
1612ff4
Fix vec to polygon conversion
geoffder May 30, 2023
507b844
add missing static to ofPolygons signature
geoffder May 30, 2023
0a3dfe1
Revert to old intro and drop unneeded decimal pts
geoffder Jun 1, 2023
b4f2605
missed intro example test reversion
geoffder Jun 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

## Users

[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.
[OpenSCAD](https://openscad.org/), [IFCjs](https://ifcjs.github.io/info/), [Grid.Space](https://grid.space/), and [OCADml](https://github.com/OCADml/OManifold) have all integrated our Manifold geometry kernel! Why? Because its reliability is guaranteed and it's 1,000 times faster than other libraries. See our [usage](https://github.com/elalish/manifold/discussions/340) and [performance](https://github.com/elalish/manifold/discussions/383) discussions for all the latest and to add your own projects & analyses.

For example, here is a log-log plot of Manifold's performance vs. earlier OpenSCAD geometry backends:

Expand All @@ -15,23 +15,31 @@ If you like OpenSCAD / JSCAD, you might also like ManifoldCAD - our own solid mo

![A metallic Menger sponge](https://elalish.github.io/manifold/samples/models/mengerSponge3.webp "A metallic Menger sponge")

### Note for Firefox users

If you find the editor is stuck on **Loading...**, setting
`dom.workers.modules.enabled: true` in your `about:config`, as mentioned in the
discussion of the
[issue#328](https://github.com/elalish/manifold/issues/328#issuecomment-1473847102)
of this repository may solve the problem.

# Manifold

[**API Documentation**](https://elalish.github.io/manifold/docs/html/modules.html) | [**Algorithm Documentation**](https://github.com/elalish/manifold/wiki/Manifold-Library) | [**Blog Posts**](https://elalish.blogspot.com/search/label/Manifold) | [**Web Examples**](https://elalish.github.io/manifold/model-viewer.html)

[Manifold](https://github.com/elalish/manifold) is a geometry library dedicated to creating and operating on manifold triangle meshes. A [manifold mesh](https://github.com/elalish/manifold/wiki/Manifold-Library#manifoldness) is a mesh that represents a solid object, and so is very important in manufacturing, CAD, structural analysis, etc. Further information can be found on the [wiki](https://github.com/elalish/manifold/wiki/Manifold-Library).

This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.
This is a modern C++ library that Github's CI verifies builds and runs on a variety of platforms. Additionally, we build bindings for JavaScript ([manifold-3d](https://www.npmjs.com/package/manifold-3d) on npm), Python, and C to make this library more portable and easy to use.

We have four core dependencies, making use of submodules to ensure compatibility:
We have four core dependencies, making use of submodules to ensure compatibility:
- `graphlite`: connected components algorithm
- `Clipper2`: provides our 2D subsystem
- `GLM`: a compact vector library
- `Thrust`: Nvidia's parallel algorithms library (basically a superset of C++17 std::parallel_algorithms)

## What's here

This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.
This library is fast with guaranteed manifold output. As such you need manifold meshes as input, which this library can create using constructors inspired by the OpenSCAD API, as well as more advanced features like smoothing and signed-distance function (SDF) level sets. You can also pass in your own mesh data, but you'll get an error status if the imported mesh isn't manifold. Various automated repair tools exist online for fixing non manifold models, usually for 3D printing.

The most significant contribution here is a guaranteed-manifold [mesh Boolean](https://github.com/elalish/manifold/wiki/Manifold-Library#mesh-boolean) algorithm, which I believe is the first of its kind. If you know of another, please open a discussion - a mesh Boolean algorithm robust to edge cases has been an open problem for many years. Likewise, if the Boolean here ever fails you, please submit an issue! This Boolean forms the basis of a CAD kernel, as it allows simple shapes to be combined into more complex ones.

Expand All @@ -41,7 +49,7 @@ Look in the [samples](https://github.com/elalish/manifold/tree/master/samples) d

## Building

Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.
Only CMake, a C++ compiler, and Python are required to be installed and set up to build this library (it has been tested with GCC, LLVM, MSVC). However, a variety of optional dependencies can bring in more functionality, see below.

Build and test (Ubuntu or similar):
```
Expand Down
23 changes: 13 additions & 10 deletions bindings/wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ project(wasm)

add_executable(manifoldjs bindings.cpp)

# make sure that we recompile the wasm when bindings.js is being modified
set_source_files_properties(bindings.cpp OBJECT_DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
set_source_files_properties(bindings.cpp PROPERTIES OBJECT_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js)
target_link_libraries(manifoldjs manifold sdf cross_section polygon)
target_compile_options(manifoldjs PRIVATE ${MANIFOLD_FLAGS})
target_link_options(manifoldjs PUBLIC --pre-js ${CMAKE_CURRENT_SOURCE_DIR}/bindings.js --bind -sALLOW_TABLE_GROWTH=1
Expand All @@ -29,15 +27,20 @@ set_target_properties(manifoldjs PROPERTIES OUTPUT_NAME "manifold")

file(MAKE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/built)

# ensure that interface files are copied over when modified
add_custom_target(js_deps ALL
geoffder marked this conversation as resolved.
Show resolved Hide resolved
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts)

add_custom_command(
TARGET manifoldjs POST_BUILD
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)
${CMAKE_CURRENT_BINARY_DIR}/manifold.*
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/built/)

add_custom_command(
TARGET manifoldjs POST_BUILD
TARGET js_deps POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
${CMAKE_CURRENT_SOURCE_DIR}/manifold*.d.ts
${CMAKE_CURRENT_SOURCE_DIR}/examples/public/)
259 changes: 72 additions & 187 deletions bindings/wasm/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,178 +14,17 @@

#include <emscripten/bind.h>
#include <emscripten/val.h>

#include <vector>

using namespace emscripten;

#include <manifold.h>
#include <polygon.h>
#include <sdf.h>

using namespace manifold;

Manifold Union(const Manifold& a, const Manifold& b) { return a + b; }

Manifold Difference(const Manifold& a, const Manifold& b) { return a - b; }

Manifold Intersection(const Manifold& a, const Manifold& b) { return a ^ b; }

Manifold UnionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Add);
}

Manifold DifferenceN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Subtract);
}

Manifold IntersectionN(const std::vector<Manifold>& manifolds) {
return Manifold::BatchBoolean(manifolds, OpType::Intersect);
}

std::vector<SimplePolygon> ToPolygon(
std::vector<std::vector<glm::vec2>>& polygons) {
std::vector<SimplePolygon> simplePolygons(polygons.size());
for (int i = 0; i < polygons.size(); i++) {
std::vector<glm::vec2> vertices(polygons[i].size());
for (int j = 0; j < polygons[i].size(); j++) {
vertices[j] = polygons[i][j];
}
simplePolygons[i] = {vertices};
}
return simplePolygons;
}

val MeshGL2JS(const MeshGL& mesh) {
val meshJS = val::object();

meshJS.set("numProp", mesh.numProp);
meshJS.set("triVerts",
val(typed_memory_view(mesh.triVerts.size(), mesh.triVerts.data()))
.call<val>("slice"));
meshJS.set("vertProperties",
val(typed_memory_view(mesh.vertProperties.size(),
mesh.vertProperties.data()))
.call<val>("slice"));
meshJS.set("mergeFromVert", val(typed_memory_view(mesh.mergeFromVert.size(),
mesh.mergeFromVert.data()))
.call<val>("slice"));
meshJS.set("mergeToVert", val(typed_memory_view(mesh.mergeToVert.size(),
mesh.mergeToVert.data()))
.call<val>("slice"));
meshJS.set("runIndex",
val(typed_memory_view(mesh.runIndex.size(), mesh.runIndex.data()))
.call<val>("slice"));
meshJS.set("runOriginalID", val(typed_memory_view(mesh.runOriginalID.size(),
mesh.runOriginalID.data()))
.call<val>("slice"));
meshJS.set("faceID",
val(typed_memory_view(mesh.faceID.size(), mesh.faceID.data()))
.call<val>("slice"));
meshJS.set("halfedgeTangent",
val(typed_memory_view(mesh.halfedgeTangent.size(),
mesh.halfedgeTangent.data()))
.call<val>("slice"));
meshJS.set("runTransform", val(typed_memory_view(mesh.runTransform.size(),
mesh.runTransform.data()))
.call<val>("slice"));

return meshJS;
}

MeshGL MeshJS2GL(const val& mesh) {
MeshGL out;
out.numProp = mesh["numProp"].as<int>();
out.triVerts = convertJSArrayToNumberVector<uint32_t>(mesh["triVerts"]);
out.vertProperties =
convertJSArrayToNumberVector<float>(mesh["vertProperties"]);
if (mesh["mergeFromVert"] != val::undefined()) {
out.mergeFromVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeFromVert"]);
}
if (mesh["mergeToVert"] != val::undefined()) {
out.mergeToVert =
convertJSArrayToNumberVector<uint32_t>(mesh["mergeToVert"]);
}
if (mesh["runIndex"] != val::undefined()) {
out.runIndex = convertJSArrayToNumberVector<uint32_t>(mesh["runIndex"]);
}
if (mesh["runOriginalID"] != val::undefined()) {
out.runOriginalID =
convertJSArrayToNumberVector<uint32_t>(mesh["runOriginalID"]);
}
if (mesh["faceID"] != val::undefined()) {
out.faceID = convertJSArrayToNumberVector<uint32_t>(mesh["faceID"]);
}
if (mesh["halfedgeTangent"] != val::undefined()) {
out.halfedgeTangent =
convertJSArrayToNumberVector<float>(mesh["halfedgeTangent"]);
}
if (mesh["runTransform"] != val::undefined()) {
out.runTransform =
convertJSArrayToNumberVector<float>(mesh["runTransform"]);
}
return out;
}

val GetMeshJS(const Manifold& manifold, const glm::ivec3& normalIdx) {
MeshGL mesh = manifold.GetMeshGL(normalIdx);
return MeshGL2JS(mesh);
}

val Merge(const val& mesh) {
val out = val::object();
MeshGL meshGL = MeshJS2GL(mesh);
bool changed = meshGL.Merge();
out.set("changed", changed);
out.set("mesh", changed ? MeshGL2JS(meshGL) : mesh);
return out;
}

Manifold FromMeshJS(const val& mesh) { return Manifold(MeshJS2GL(mesh)); }

Manifold Smooth(const val& mesh,
const std::vector<Smoothness>& sharpenedEdges = {}) {
return Manifold::Smooth(MeshJS2GL(mesh), sharpenedEdges);
}

Manifold Extrude(std::vector<std::vector<glm::vec2>>& polygons, float height,
int nDivisions, float twistDegrees, glm::vec2 scaleTop) {
return Manifold::Extrude(ToPolygon(polygons), height, nDivisions,
twistDegrees, scaleTop);
}

Manifold Revolve(std::vector<std::vector<glm::vec2>>& polygons,
int circularSegments) {
return Manifold::Revolve(ToPolygon(polygons), circularSegments);
}

Manifold Transform(Manifold& manifold, const val& mat) {
std::vector<float> array = convertJSArrayToNumberVector<float>(mat);
glm::mat4x3 matrix;
for (const int col : {0, 1, 2, 3})
for (const int row : {0, 1, 2}) matrix[col][row] = array[col * 4 + row];
return manifold.Transform(matrix);
}

Manifold Warp(Manifold& manifold, uintptr_t funcPtr) {
void (*f)(glm::vec3&) = reinterpret_cast<void (*)(glm::vec3&)>(funcPtr);
return manifold.Warp(f);
}
#include <vector>

Manifold SetProperties(Manifold& manifold, int numProp, uintptr_t funcPtr) {
void (*f)(float*, glm::vec3, const float*) =
reinterpret_cast<void (*)(float*, glm::vec3, const float*)>(funcPtr);
return manifold.SetProperties(numProp, f);
}
#include "cross_section.h"
#include "helpers.cpp"

Manifold LevelSetJs(uintptr_t funcPtr, Box bounds, float edgeLength,
float level) {
float (*f)(const glm::vec3&) =
reinterpret_cast<float (*)(const glm::vec3&)>(funcPtr);
Mesh m = LevelSet(f, bounds, edgeLength, level);
return Manifold(m);
}
using namespace emscripten;
using namespace manifold;

EMSCRIPTEN_BINDINGS(whatever) {
value_object<glm::vec2>("vec2")
Expand Down Expand Up @@ -224,6 +63,18 @@ EMSCRIPTEN_BINDINGS(whatever) {
.value("FaceIDWrongLength", Manifold::Error::FaceIDWrongLength)
.value("InvalidConstruction", Manifold::Error::InvalidConstruction);

enum_<CrossSection::FillRule>("fillrule")
.value("EvenOdd", CrossSection::FillRule::EvenOdd)
.value("NonZero", CrossSection::FillRule::NonZero)
.value("Positive", CrossSection::FillRule::Positive)
.value("Negative", CrossSection::FillRule::Negative);

enum_<CrossSection::JoinType>("jointype")
.value("Square", CrossSection::JoinType::Square)
.value("Round", CrossSection::JoinType::Round)
.value("Miter", CrossSection::JoinType::Miter);

value_object<Rect>("rect").field("min", &Rect::min).field("max", &Rect::max);
value_object<Box>("box").field("min", &Box::min).field("max", &Box::max);

value_object<Smoothness>("smoothness")
Expand All @@ -247,21 +98,54 @@ EMSCRIPTEN_BINDINGS(whatever) {
register_vector<glm::vec2>("Vector_vec2");
register_vector<std::vector<glm::vec2>>("Vector2_vec2");
register_vector<float>("Vector_f32");
register_vector<CrossSection>("Vector_crossSection");
register_vector<Manifold>("Vector_manifold");
register_vector<Smoothness>("Vector_smoothness");
register_vector<glm::vec4>("Vector_vec4");

class_<CrossSection>("CrossSection")
.constructor(&cross_js::OfPolygons)
.function("_add", &cross_js::Union)
.function("_subtract", &cross_js::Difference)
.function("_intersect", &cross_js::Intersection)
.function("_Warp", &cross_js::Warp)
.function("transform", &cross_js::Transform)
.function("_Translate", &CrossSection::Translate)
.function("_Rotate", &CrossSection::Rotate)
.function("_Scale", &CrossSection::Scale)
.function("_Mirror", &CrossSection::Mirror)
.function("_Decompose", &CrossSection::Decompose)
.function("isEmpty", &CrossSection::IsEmpty)
.function("area", &CrossSection::Area)
.function("numVert", &CrossSection::NumVert)
.function("numContour", &CrossSection::NumContour)
.function("_Bounds", &CrossSection::Bounds)
.function("simplify", &CrossSection::Simplify)
.function("_Offset", &cross_js::Offset)
.function("_RectClip", &CrossSection::RectClip)
.function("_ToPolygons", &CrossSection::ToPolygons);

// CrossSection Static Methods
function("_Square", &CrossSection::Square);
function("_Circle", &CrossSection::Circle);
function("_crossSectionCompose", &CrossSection::Compose);
function("_crossSectionUnionN", &cross_js::UnionN);
function("_crossSectionDifferenceN", &cross_js::DifferenceN);
function("_crossSectionIntersectionN", &cross_js::IntersectionN);

class_<Manifold>("Manifold")
.constructor(&FromMeshJS)
.function("add", &Union)
.function("subtract", &Difference)
.function("intersect", &Intersection)
.constructor(&man_js::FromMeshJS)
.function("add", &man_js::Union)
.function("subtract", &man_js::Difference)
.function("intersect", &man_js::Intersection)
.function("_Split", &man_js::Split)
.function("_SplitByPlane", &man_js::SplitByPlane)
.function("_TrimByPlane", &Manifold::TrimByPlane)
.function("_GetMeshJS", &GetMeshJS)
.function("_GetMeshJS", &js::GetMeshJS)
.function("refine", &Manifold::Refine)
.function("_Warp", &Warp)
.function("_SetProperties", &SetProperties)
.function("transform", &Transform)
.function("_Warp", &man_js::Warp)
.function("_SetProperties", &man_js::SetProperties)
.function("transform", &man_js::Transform)
.function("_Translate", &Manifold::Translate)
.function("_Rotate", &Manifold::Rotate)
.function("_Scale", &Manifold::Scale)
Expand All @@ -281,25 +165,26 @@ EMSCRIPTEN_BINDINGS(whatever) {
.function("originalID", &Manifold::OriginalID)
.function("asOriginal", &Manifold::AsOriginal);

// Manifold Static Methods
function("_Cube", &Manifold::Cube);
function("_Cylinder", &Manifold::Cylinder);
function("_Sphere", &Manifold::Sphere);
function("tetrahedron", &Manifold::Tetrahedron);
function("_Smooth", &Smooth);
function("_Extrude", &Extrude);
function("_Tetrahedron", &Manifold::Tetrahedron);
geoffder marked this conversation as resolved.
Show resolved Hide resolved
function("_Smooth", &js::Smooth);
function("_Extrude", &Manifold::Extrude);
geoffder marked this conversation as resolved.
Show resolved Hide resolved
function("_Triangulate", &Triangulate);
function("_Revolve", &Revolve);
function("_LevelSet", &LevelSetJs);
function("_Merge", &Merge);

function("_unionN", &UnionN);
function("_differenceN", &DifferenceN);
function("_intersectionN", &IntersectionN);
function("_Compose", &Manifold::Compose);

function("_Revolve", &Manifold::Revolve);
function("_LevelSet", &man_js::LevelSet);
function("_Merge", &js::Merge);
function("_ReserveIDs", &Manifold::ReserveIDs);
function("_manifoldCompose", &Manifold::Compose);
function("_manifoldUnionN", &man_js::UnionN);
function("_manifoldDifferenceN", &man_js::DifferenceN);
function("_manifoldIntersectionN", &man_js::IntersectionN);

// Quality Globals
function("setMinCircularAngle", &Quality::SetMinCircularAngle);
function("setMinCircularEdgeLength", &Quality::SetMinCircularEdgeLength);
function("setCircularSegments", &Quality::SetCircularSegments);
function("getCircularSegments", &Quality::GetCircularSegments);
function("reserveIDs", &Manifold::ReserveIDs);
}
Loading