diff --git a/docs/blog/2024-04-23-introducing-bitbybit-runner.md b/docs/blog/2024-04-23-introducing-bitbybit-runner.md index 42644b5f..d478ea20 100644 --- a/docs/blog/2024-04-23-introducing-bitbybit-runner.md +++ b/docs/blog/2024-04-23-introducing-bitbybit-runner.md @@ -8,6 +8,22 @@ description: "Containment has failed! Run your Bitbybit.dev scripts wherever you ![Runner is on the loose. A free and powerful tiger in the jungle playing with soap bubbles, symbolizing the freedom to run scripts anywhere.](https://ik.imagekit.io/bitbybit/app/assets/blog/introducing-bitbybit-runner/introducing-bitbybit-runner.jpeg "Runner is on the loose. Run your scripts wherever you are on the internet") +:::info Update - January 2026 + +Since this post was originally published, **Bitbybit Runner has significantly evolved**. What started as a single Babylon.js-based runner has now expanded to support **multiple game engines**: + +- **Babylon.js Runner** - The original implementation (full and lite variants) +- **Three.js Runner** - Support for the popular Three.js library (full and lite variants) +- **PlayCanvas Runner** - Integration with the PlayCanvas game engine (full and lite variants) + +Each runner comes in two variants: +- **Full Runner** - Includes the game engine in the bundle for quick setup +- **Lite Runner** - Excludes the game engine, allowing you to load it separately for better flexibility and smaller bundle sizes + +Learn more about using runners with different engines in our [comprehensive runner documentation](/learn/runners/intro). + +::: + In the last couple of weeks, we've been discussing and experimenting with a new way to run and embed Bitbybit.dev scripts. The result of these experiments is a new tool called **BITBYBIT-RUNNER.JS**. You can now run your scripts on your own websites, blogs, webshops, or third-party coding sites! diff --git a/docs/blog/2024-11-08-updated-bitbybit-runners.md b/docs/blog/2024-11-08-updated-bitbybit-runners.md index 55c2613e..3c47dcda 100644 --- a/docs/blog/2024-11-08-updated-bitbybit-runners.md +++ b/docs/blog/2024-11-08-updated-bitbybit-runners.md @@ -12,7 +12,11 @@ Our **Bitbybit Runners** allow you to easily include Bitbybit.dev's powerful 3D -Furthermore, we've developed new **Lite versions** of the runners for both ThreeJS and BabylonJS. These Lite versions allow you to load the respective game engines separately if you're already including them in your project, significantly reducing bundle sizes. Learn more about these exciting updates in this blog post. +:::info Update: January 6, 2026 +This article has been updated to include information about our new **PlayCanvas Runner**! PlayCanvas is now the third officially supported game engine for Bitbybit Runners, joining BabylonJS and ThreeJS. All the concepts about Lite versions and runner architecture described below now apply to PlayCanvas as well. [Read the full PlayCanvas announcement here](/blog/playcanvas-support). +::: + +Furthermore, we've developed new **Lite versions** of the runners for both ThreeJS and BabylonJS (and now PlayCanvas!). These Lite versions allow you to load the respective game engines separately if you're already including them in your project, significantly reducing bundle sizes. Learn more about these exciting updates in this blog post. ### Brief History of the Runner @@ -23,21 +27,23 @@ About six months ago, we introduced the first version of the `bitbybit-runner`, If you're unfamiliar with how the runner works or want a refresher, be sure to check out our original introduction article: ➡️ **[Introducing BITBYBIT-RUNNER.JS](/blog/introducing-bitbybit-runner)** -### New and Improved Runners: Enter ThreeJS! +### New and Improved Runners: Enter ThreeJS! (And Later, PlayCanvas!) Recently, we [announced official support for the ThreeJS game engine](/blog/threejs-support) via new NPM packages. Shortly after, we received requests from the community for a dedicated runner specifically for ThreeJS. Based on your valuable feedback, we are thrilled to announce the release of a **new Bitbybit Runner for ThreeJS**! +**Update 2026:** Following the same principles, we've now also released a [PlayCanvas Runner](/blog/playcanvas-support), bringing the total number of supported game engines to three! + This new runner follows the same core principles as the BabylonJS version but is specifically designed and optimized to work with ThreeJS. Like the original, it’s a single JavaScript file you can add to your website. The runner automatically loads all necessary resources, including web workers, CAD kernels, and other dependencies required to run your Bitbybit.dev scripts within a ThreeJS environment. While developing the ThreeJS runner, we also realized that many projects already include ThreeJS (or BabylonJS) as a dependency in their existing build setups. To avoid the redundancy of loading the entire game engine again from the runner’s bundled JavaScript file, we addressed this with a new, more flexible solution. ### Introducing "Lite" Versions of the Runners -We’ve created **Lite versions** of our runners for both ThreeJS and BabylonJS. These versions allow you to load the respective game engines *separately* if they are already part of your project, thus reducing redundancy and improving load times. +We've created **Lite versions** of our runners for BabylonJS, ThreeJS, and PlayCanvas. These versions allow you to load the respective game engines *separately* if they are already part of your project, thus reducing redundancy and improving load times. -The Lite bundles are significantly smaller because they **do not include the game engine dependencies** themselves. Instead, they expect the game engine (ThreeJS or BabylonJS) to be available in the global scope (e.g., on the `window` object). +The Lite bundles are significantly smaller because they **do not include the game engine dependencies** themselves. Instead, they expect the game engine (BabylonJS, ThreeJS, or PlayCanvas) to be available in the global scope (e.g., on the `window` object). -If your project already loads ThreeJS or BabylonJS via a script tag, NPM import, or another method, using the Lite version of the corresponding Bitbybit Runner will prevent duplicate loading of the engine, making your website faster and more efficient. +If your project already loads one of these game engines via a script tag, NPM import, or another method, using the Lite version of the corresponding Bitbybit Runner will prevent duplicate loading of the engine, making your website faster and more efficient. ### ThreeJS Runners: Examples in Action @@ -88,22 +94,42 @@ Here's a summary of the Bitbybit Runners now available: * A lightweight version of the ThreeJS runner that **does not include** the ThreeJS engine. * It expects the `window` object to contain these global ThreeJS dependencies before initialization: `THREE` (for ThreeJS itself) and `OrbitControls` (if you plan to use them via the runner's setup). Only then can this Lite runner initialize successfully. * Use this if ThreeJS is already loaded in your project. +* **`bitbybit-runner-playcanvas.js`** *(Added 2026)* + * This runner executes all open-source Bitbybit.dev CAD algorithms within a PlayCanvas environment. It bundles the PlayCanvas engine in a single file. + * Optimized for high performance and mobile devices. + * Can also execute visual scripts exported from our editors, as long as they do not contain BabylonJS-specific logic. +* **`bitbybit-runner-lite-playcanvas.js`** *(Added 2026)* + * A lightweight version of the PlayCanvas runner that **does not include** the PlayCanvas engine. + * It expects the `window` object to contain the `pc` (PlayCanvas) global before initialization. + * Use this if PlayCanvas is already loaded in your project. -### How BabylonJS Runners Differ from ThreeJS Runners +### How BabylonJS, ThreeJS, and PlayCanvas Runners Differ -Both sets of runners (BabylonJS and ThreeJS) can execute all of our open-source 3D modeling algorithms and interact with our CAD kernels (OCCT, JSCAD). The primary differences are related to the game engines themselves and their specific features: +All three runner families (BabylonJS, ThreeJS, and PlayCanvas) can execute all of our open-source 3D modeling algorithms and interact with our CAD kernels (OCCT, JSCAD, Manifold). The primary differences are related to the game engines themselves and their specific features: * **Visual Script Compatibility:** If you are looking to export and run scripts created with our visual programming editors (Rete, Blockly) on the Bitbybit.dev website, you will generally have more success and feature parity with the **BabylonJS runner**. Our visual editors are natively based on BabylonJS. Thus, if your scripts use specific BabylonJS features (like our built-in skyboxes, GUI elements, Havok physics integrations, specific BabylonJS materials, etc.), you should use a BabylonJS runner. -* **Error Handling for Visual Scripts in ThreeJS Runner:** The ThreeJS runner will also attempt to run visual scripts exported from Bitbybit.dev. However, if it encounters BabylonJS-specific logic, it will throw an error as those features are not available in a ThreeJS context. That said, if you limit your visual coding to *only* contain 3D modeling-related features (CAD operations, basic geometry, etc.) without engine-specific rendering features, then your scripts *should* execute just fine with the ThreeJS runner. -* **Direct JavaScript Coding:** If you are simply writing Bitbybit.dev logic directly in JavaScript (not exporting from visual editors), then either of these runners (BabylonJS or ThreeJS, full or Lite) will work well for you for the core CAD functionalities. -* **Bundle Size:** You will notice that the bundle size of the full BabylonJS runner is larger than the full ThreeJS runner. This is something to consider when deciding which runner to use, especially if bundle size is a critical factor for your project. The Lite versions, of course, are much smaller for both. - -No matter your preference, we love both of these incredible game engines, and we are excited to see what you will create with them using Bitbybit.dev! +* **Error Handling for Visual Scripts in ThreeJS and PlayCanvas Runners:** The ThreeJS and PlayCanvas runners will also attempt to run visual scripts exported from Bitbybit.dev. However, if they encounter BabylonJS-specific logic, they will throw an error as those features are not available in their contexts. That said, if you limit your visual coding to *only* contain 3D modeling-related features (CAD operations, basic geometry, etc.) without engine-specific rendering features, then your scripts *should* execute just fine with any of the three runners. +* **Direct JavaScript Coding:** If you are simply writing Bitbybit.dev logic directly in JavaScript (not exporting from visual editors), then any of these runners (BabylonJS, ThreeJS, or PlayCanvas, full or Lite) will work well for you for the core CAD functionalities. +* **Bundle Size & Performance:** + * The BabylonJS runner is generally the largest due to its comprehensive feature set. + * The ThreeJS runner offers a good balance of features and size. + * The PlayCanvas runner is optimized for performance and smaller bundle sizes, making it ideal for mobile-first applications. + * The Lite versions are much smaller for all three engines. +* **Engine-Specific Strengths:** + * **BabylonJS:** Best for projects requiring physics (Havok), GUI elements, and maximum compatibility with visual scripts. + * **ThreeJS:** Excellent for projects already using ThreeJS or needing its vast ecosystem. + * **PlayCanvas:** Perfect for high-performance applications, mobile optimization, and game development. + +No matter your preference, we love all three of these incredible game engines, and we are excited to see what you will create with them using Bitbybit.dev! ### Where to Find the Runners We are serving the Bitbybit Runners from the **JSDelivr CDN**. You can include them in your website with the following script tags. Remember to replace `` with the actual version you intend to use. +:::tip Self-Hosting for Production +For production applications requiring maximum reliability and control, consider [**self-hosting the runners and assets**](/learn/hosting-and-cdn) on your own infrastructure. +::: + * **`bitbybit-runner-babylonjs.js`** (Full BabylonJS runner) ```html @@ -120,6 +146,14 @@ We are serving the Bitbybit Runners from the **JSDelivr CDN**. You can include t ```html ``` +* **`bitbybit-runner-playcanvas.js`** (Full PlayCanvas runner) *(Added 2026)* + ```html + + ``` +* **`bitbybit-runner-lite-playcanvas.js`** (Lite PlayCanvas runner - PlayCanvas must be loaded separately) *(Added 2026)* + ```html + + ``` **Note:** You should replace `` with an actual version number (e.g., `0.21.0`). You can find all the official versions of Bitbybit.dev here: ➡️ **[Bitbybit.dev GitHub Releases](https://github.com/bitbybit-dev/bitbybit/releases)** @@ -128,9 +162,9 @@ We are serving the Bitbybit Runners from the **JSDelivr CDN**. You can include t It can be hard to guess how runners can be used in practice, which is why we provide you with actual, working examples. We create projects that fit within a single `index.html` file, which you can simply open in your browser directly from your file system. This approach doesn't even require you to run a local server on your computer. That’s the true power of the web! -➡️ **[Explore Example Use Cases of BabylonJS & ThreeJS Runners on GitHub](https://github.com/bitbybit-dev/app-examples/tree/main/runner)** +➡️ **[Explore Example Use Cases of BabylonJS, ThreeJS & PlayCanvas Runners on GitHub](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner)** -This GitHub repository contains examples that use both the full and Lite runners. We also provide examples on how to initialize BabylonJS or ThreeJS scene objects *outside* the context of the runner. This approach allows Bitbybit Runners to work more harmoniously with third-party tools or existing projects that already manage their own game engine instances (e.g., integrating with A-Frame or a custom ThreeJS setup). +This GitHub repository contains examples that use both the full and Lite runners for all three game engines. We also provide examples on how to initialize BabylonJS, ThreeJS, or PlayCanvas scene objects *outside* the context of the runner. This approach allows Bitbybit Runners to work more harmoniously with third-party tools or existing projects that already manage their own game engine instances (e.g., integrating with A-Frame, custom ThreeJS setups, or PlayCanvas Editor projects). Initiating scene objects *inside* the runner (letting the runner create the default scene) is also possible and is often simpler, though it offers less fine-grained control if you have a complex existing setup. diff --git a/docs/blog/2026-01-06-playcanvas-support.md b/docs/blog/2026-01-06-playcanvas-support.md new file mode 100644 index 00000000..ce79dd15 --- /dev/null +++ b/docs/blog/2026-01-06-playcanvas-support.md @@ -0,0 +1,235 @@ +--- +slug: playcanvas-support +title: "ANNOUNCING PlayCanvas Support: High-Performance 3D CAD for Game Developers" +authors: [ubarevicius] +tags: [bitbybit, cad, playcanvas] +description: "Introducing @bitbybit-dev/playcanvas NPM package and PlayCanvas Runner - bringing professional CAD capabilities to one of the web's most powerful game engines." +--- + +![Playcanvas and Bitbybit logos.](https://ik.imagekit.io/bitbybit/app/assets/blog/playcanvas-support/playcanvas-and-bitbybit-dev.webp "Playcanvas and Bitbybit logos") + +We are excited to announce the release of our new open-source NPM package and runner for **PlayCanvas**, one of the most performant and feature-rich WebGL game engines available. The [**`@bitbybit-dev/playcanvas`**](https://www.npmjs.com/package/@bitbybit-dev/playcanvas) package and [**PlayCanvas Runner**](/learn/runners/engines/playcanvas/full-runner) continue our mission to make professional 3D CAD accessible across the entire web development ecosystem, now empowering PlayCanvas developers with industrial-grade geometric modeling capabilities. + + + +**Explore the New Package:** +* NPM: **[@bitbybit-dev/playcanvas](https://www.npmjs.com/package/@bitbybit-dev/playcanvas)** +* NPM (Core Layer): **[@bitbybit-dev/core](https://www.npmjs.com/package/@bitbybit-dev/core)** +* GitHub: **[Bitbybit.dev Monorepo](https://github.com/bitbybit-dev/bitbybit)** +* Documentation: **[PlayCanvas Integration Guide](/learn/npm-packages/playcanvas)** + +### Why PlayCanvas? + +PlayCanvas has established itself as a leading choice for web-based game development and interactive 3D experiences. Its entity-component architecture, visual editor, and exceptional runtime performance make it ideal for creating everything from games to product configurators. However, when it comes to precise CAD operations, parametric modeling, and industrial-grade geometry processing, developers have traditionally needed to look elsewhere. + +With Bitbybit's PlayCanvas integration, that changes. Now, PlayCanvas developers can leverage: +* **Industrial CAD Kernels**: Access to OpenCascade (OCCT), JSCAD, and Manifold geometry engines +* **Parametric Modeling**: Create complex, constraint-based 3D models that update dynamically +* **Boolean Operations**: Precise union, intersection, and difference operations on solid geometry +* **Advanced Surfaces**: Lofting, sweeping, filleting, and chamfering operations +* **CAD File I/O**: Import and export STEP, IGES, STL, and other industry-standard formats + +All of this power is now available within PlayCanvas's entity-component system, maintaining the engine's clean architecture and high performance. + +### The PlayCanvas Runner: Instant CAD Anywhere + +Just like our ThreeJS and BabylonJS runners, we're releasing a **PlayCanvas Runner** that allows you to execute Bitbybit CAD scripts on any website using a single JavaScript file from our CDN. This means you can: +* Create 3D product configurators for e-commerce sites +* Build interactive parametric design tools +* Embed CAD modeling capabilities in documentation or tutorials +* Prototype complex geometries without complex build setups + +The PlayCanvas Runner follows the same architecture as our other runners, supporting both full and lite versions: + +* **`bitbybit-runner-playcanvas.js`**: + * Includes everything needed to run Bitbybit scripts, including the PlayCanvas engine itself + * Perfect for quick integration when PlayCanvas isn't already part of your project + * Bundles CAD kernels, web workers, and all dependencies +* **`bitbybit-runner-lite-playcanvas.js`**: + * Lightweight version that expects PlayCanvas to be loaded separately + * Ideal for projects already using PlayCanvas + * Significantly smaller bundle size + * Expects `pc` (PlayCanvas) to be available as `window.PLAYCANVAS` + +### How to Include the PlayCanvas Runner + +The runners are served from the **jsDelivr CDN**. Include them in your website with these script tags (replace `` with the actual version): + +:::tip Self-Hosting +For production applications, consider [**self-hosting the runners and assets**](/learn/hosting-and-cdn) for improved reliability and performance. +::: + +* **PlayCanvas Runner (Full):** + ```html + + ``` +* **PlayCanvas Runner (Lite):** + ```html + + ``` + +You can find all official Bitbybit versions on [GitHub Releases](https://github.com/bitbybit-dev/bitbybit/releases). + +### Using the PlayCanvas Runner + +Once included, the runner is accessible via `window.bitbybitRunner`: + +```javascript +// Get the runner instance +const runner = window.bitbybitRunner.getRunnerInstance(); + +// Initialize the runner with your canvas +const { bitbybit, Bit, camera, scene, app, pc } = await runner.run({ + canvasId: 'playcanvas-canvas', + canvasZoneClass: 'myCanvasZone', + // Configure which kernels to enable + enableOCCT: true, + enableJSCAD: true, + enableManifold: true, + // Optional scene configuration + cameraPosition: [20, 10, 20], + cameraTarget: [0, 0, 0], + backgroundColor: '#1a1c1f', +}); + +// Now you can use bitbybit directly for CAD operations +const cube = await bitbybit.occt.shapes.solid.createCube({ size: 10, center: [0, 0, 0] }); +await bitbybit.draw.drawAnyAsync({ entity: cube }); + +// Or execute an exported script with inputs +const results = await runner.executeScript( + yourExportedScript, + { + // Pass inputs to your script + width: 10, + height: 5, + depth: 3 + } +); + +// Access outputs from your script +console.log(results); +``` + +### Exporting Scripts for the PlayCanvas Runner + +All Bitbybit visual programming editors (Rete, Blockly, TypeScript) support exporting scripts compatible with the PlayCanvas Runner. The export process is identical to other engines: + +1. Open your script in any Bitbybit editor +2. Click the "More Actions" menu +3. Select "Export to Runner" +4. Choose "PlayCanvas" as your target engine +5. Copy or download the generated JavaScript code + +Your exported script can then be executed by the PlayCanvas Runner on any website, in any coding environment (StackBlitz, CodePen, JSFiddle), or embedded in your own applications. + +### Comparing Runners: BabylonJS, ThreeJS, and PlayCanvas + +All three runner families share the same core CAD capabilities: +* Full access to OCCT, JSCAD, and Manifold kernels +* Identical parametric modeling operations +* Same file import/export capabilities +* Compatible with visual scripts from Bitbybit editors (with some engine-specific considerations) + +The primary differences relate to the game engines themselves: + +**BabylonJS Runner:** +* Best compatibility with visual scripts from Bitbybit.dev editors (natively BabylonJS-based) +* Includes physics (Havok), GUI elements, and advanced materials out of the box +* Largest bundle size +* Ideal for projects requiring BabylonJS-specific features + +**ThreeJS Runner:** +* Vast ecosystem and community +* Smaller bundle size than BabylonJS +* Excellent for projects already using ThreeJS +* Good compatibility with visual scripts (unless they use BabylonJS-specific features) + +**PlayCanvas Runner:** +* Exceptional performance and mobile optimization +* Entity-component architecture familiar to game developers +* Cloud-based visual editor available +* Professional game development features +* Excellent for real-time applications and games requiring CAD precision + +### Architecture: Engine-Agnostic Core + +Our PlayCanvas integration maintains the same modular, engine-agnostic architecture we established with ThreeJS and BabylonJS support: + +![Bitbybit platform architecture showing the engine-agnostic core with PlayCanvas, ThreeJS, and BabylonJS integration layers](https://ik.imagekit.io/bitbybit/app/assets/npm-package-architecture.jpeg "Bitbybit architecture with PlayCanvas support") + +The **`@bitbybit-dev/core`** package contains all fundamental CAD logic, data structures, and interfaces. Each game engine-specific package (`@bitbybit-dev/playcanvas`, `@bitbybit-dev/threejs`, `@bitbybit-dev/babylonjs`) provides a thin integration layer that translates between Bitbybit's CAD operations and the respective engine's rendering system. + +This architecture means: +* **Consistent API**: Learn once, use everywhere +* **Easy Migration**: Switch engines if project requirements change +* **Future-Proof**: New engine integrations can be added without disrupting existing code +* **Optimal Performance**: Each integration is specifically optimized for its target engine + +### Real-World Use Cases with PlayCanvas + +PlayCanvas's performance characteristics make it particularly well-suited for certain applications: + +**3D Product Configurators:** +PlayCanvas's mobile-first optimization is perfect for e-commerce product configurators that need to run smoothly on smartphones and tablets. Combined with Bitbybit's parametric modeling, you can create configurators for furniture, jewelry, mechanical parts, and more. + +**Architectural Visualizations:** +Create interactive architectural presentations with parametric building components. Users can adjust dimensions, materials, and layouts in real-time while maintaining structural precision. + +**Engineering Applications:** +Build web-based CAD tools for mechanical engineering, allowing engineers to design, test, and share components directly in the browser without specialized software. + +**Educational Tools:** +Develop interactive geometry and mathematics education tools that demonstrate CAD concepts with high performance even on limited hardware. + +**Game Integration:** +Generate procedural game assets with CAD precision, create customizable in-game items, or build level editors that use industrial-grade geometry operations. + +### Getting Started + +To start using Bitbybit with PlayCanvas: + +1. **Via NPM** (for build-based projects): + ```bash + npm install @bitbybit-dev/playcanvas + ``` + Then follow our [PlayCanvas Integration Guide](/learn/npm-packages/playcanvas/start-with-playcanvas) + +2. **Via Runner** (for quick integration): + Include the runner script tag and start executing your CAD scripts immediately. Check our [Runner documentation](/learn/runners/engines/playcanvas/full-runner) and [examples on GitHub](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/playcanvas) + +3. **Visual Programming**: + Create your geometry in Bitbybit's visual editors, export to the PlayCanvas Runner, and integrate with your own UI + +### Examples and Resources + +We've prepared comprehensive documentation and examples: +* **[PlayCanvas Starter Template](/learn/npm-packages/playcanvas/start-with-playcanvas)**: Complete setup guide with Vite +* **[Advanced Parametric Model](/learn/npm-packages/playcanvas/advanced-parametric-3d-model)**: Complex parametric modeling with GUI +* **[Hex House Concept](/learn/npm-packages/playcanvas/hex-house-concept)**: Architectural parametric design showcase +* **[GitHub Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/vite/playcanvas)**: Working code samples + +### PlayCanvas is Here to Stay + +Just like BabylonJS and ThreeJS, PlayCanvas is now a first-class citizen in the Bitbybit ecosystem. We're committed to maintaining and improving this integration, ensuring PlayCanvas developers have access to the same powerful CAD capabilities available on other platforms. + +This expansion continues our vision of making professional 3D CAD truly accessible and adaptable for the entire web development community. Whether you're a game developer, product designer, engineer, or educator, you now have more choices than ever for bringing precise, parametric 3D modeling to your web projects. + +### What's Next? + +We're actively working on: +* More PlayCanvas-specific examples and tutorials +* Additional integration with PlayCanvas's visual editor +* Enhanced support for PlayCanvas-specific features in visual scripts + +We're excited to see what the PlayCanvas community will create with these new CAD capabilities. Share your projects with us on [Discord](https://discord.gg/GSe3VMe), tag us on social media, or contribute examples to our GitHub repository! + +--- + +**Ready to get started?** +* 📦 **[Install @bitbybit-dev/playcanvas](https://www.npmjs.com/package/@bitbybit-dev/playcanvas)** +* 📚 **[Read the Documentation](/learn/npm-packages/playcanvas)** +* 🎮 **[Try the PlayCanvas Runner](/learn/runners/engines/playcanvas/full-runner)** +* 💬 **[Join our Discord Community](https://discord.gg/GSe3VMe)** + +Happy coding, and welcome to the PlayCanvas community in Bitbybit! 🎮✨ diff --git a/docs/blog/tags.yml b/docs/blog/tags.yml index 08c22079..0f7a5bb9 100644 --- a/docs/blog/tags.yml +++ b/docs/blog/tags.yml @@ -1,32 +1,32 @@ bitbybit: label: Bitbybit permalink: /bitbybit - description: Blogs about Bitbybit + description: Blog posts about Bitbybit cloud: label: Cloud permalink: /cloud - description: Blogs about Cloud + description: Blog posts about Cloud ai: label: AI permalink: /ai - description: Blogs about Artificial Intelligence + description: Blog posts about Artificial Intelligence cad: label: CAD permalink: /cad - description: Blogs about Computer-Aided Design + description: Blog posts about Computer-Aided Design xr: label: XR permalink: /xr - description: Blogs about Extended Reality + description: Blog posts about Extended Reality vr: label: VR permalink: /vr - description: Blogs about Virtual Reality + description: Blog posts about Virtual Reality 3d-bits: label: 3D Bits @@ -36,7 +36,7 @@ vr: shopify: label: Shopify permalink: /shopify - description: Blogs about Shopify integrations via 3D Bits app + description: Blog posts about Shopify integrations via 3D Bits app case-study: label: Case Study @@ -46,4 +46,19 @@ case-study: fitness-equipment: label: Fitness Equipment permalink: /fitness-equipment - description: Blogs about fitness equipment industry and 3D visualization \ No newline at end of file + description: Blog posts about fitness equipment industry and 3D visualization + +babylonjs: + label: babylonjs + permalink: /babylonjs + description: Blog posts about Babylon.js 3D engine + +threejs: + label: threejs + permalink: /threejs + description: Blog posts about Three.js 3D library + +playcanvas: + label: PlayCanvas + permalink: /playcanvas + description: Blog posts about PlayCanvas 3D engine \ No newline at end of file diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index 9b29c107..bc486c64 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -88,6 +88,7 @@ const config: Config = { { to: "/learn/3d-bits/intro", label: "3D Bits", position: "left" }, { to: "/learn/npm-packages/threejs", label: "ThreeJS", position: "left" }, { to: "/learn/npm-packages/babylonjs", label: "BabylonJS", position: "left" }, + { to: "/learn/npm-packages/playcanvas", label: "PlayCanvas", position: "left" }, { to: "/blog", label: "Blog", position: "left" }, { to: "https://bitbybit.dev", label: "Home", position: "left" }, { diff --git a/docs/learn/3d-bits/theme-app-extensions/bitbybit-apps.md b/docs/learn/3d-bits/theme-app-extensions/bitbybit-apps.md index abd5f6c5..c9a249a8 100644 --- a/docs/learn/3d-bits/theme-app-extensions/bitbybit-apps.md +++ b/docs/learn/3d-bits/theme-app-extensions/bitbybit-apps.md @@ -115,11 +115,11 @@ Deviating significantly from our recommended approach and templates is done at y To get you started quickly, we provide access to our private GitHub repository containing production-ready templates: - **Vanilla TypeScript + React Three Fiber** -- **Vanilla TypeScript + Three.js** -- **Vanilla TypeScript + Babylon.js** +- **Vanilla TypeScript + ThreeJS** +- **Vanilla TypeScript + BabylonJS** - **Vanilla TypeScript + PlayCanvas** -- **Vanilla TypeScript + Bitbybit CAD & Three.js** -- **Vanilla TypeScript + Bitbybit CAD & Babylon.js** +- **Vanilla TypeScript + Bitbybit CAD & ThreeJS** +- **Vanilla TypeScript + Bitbybit CAD & BabylonJS** *(More templates focusing on other technologies may be added over time.)* diff --git a/docs/learn/3d-bits/tutorials/bitbybit-apps/settings.md b/docs/learn/3d-bits/tutorials/bitbybit-apps/settings.md index c8cc6bd2..dba09eca 100644 --- a/docs/learn/3d-bits/tutorials/bitbybit-apps/settings.md +++ b/docs/learn/3d-bits/tutorials/bitbybit-apps/settings.md @@ -174,8 +174,8 @@ We provide production-ready templates in our private GitHub repository (included 1. **Clone a template** from our private GitHub repository: - Vanilla TypeScript + React Three Fiber - - Vanilla TypeScript + Three.js - - Vanilla TypeScript + Babylon.js + - Vanilla TypeScript + ThreeJS + - Vanilla TypeScript + BabylonJS - Vanilla TypeScript + PlayCanvas - Vanilla TypeScript + Bitbybit CAD diff --git a/docs/learn/about.md b/docs/learn/about.md index 7f848958..96e1a412 100644 --- a/docs/learn/about.md +++ b/docs/learn/about.md @@ -88,7 +88,7 @@ Our online school is dedicated to teaching programming, mathematics, virtual rea Beyond our hosted platform, we empower developers to take our core technology into their own hands. -* **Open-Source NPM Packages:** We provide a suite of MIT-licensed NPM packages that expose our underlying geometry kernels (OCCT, JSCAD, Manifold) and rendering integrations (e.g., for ThreeJS, BabylonJS). This allows you to build completely custom 3D applications, tools, or embed specific functionalities into your existing websites. +* **Open-Source NPM Packages:** We provide a suite of MIT-licensed NPM packages that expose our underlying geometry kernels (OCCT, JSCAD, Manifold) and rendering integrations (e.g., for ThreeJS, BabylonJS, PlayCanvas). This allows you to build completely custom 3D applications, tools, or embed specific functionalities into your existing websites. * Learn more: [**Our NPM Packages**](/learn/npm-packages/intro) {/* Link to your NPM packages page */} * **Runners:** For even simpler integration of pre-built Bitbybit scripts or functionalities into web pages, we offer runners that can execute these scripts within a defined context on your site. diff --git a/docs/learn/code/common/occt/operations/advanced-loft.md b/docs/learn/code/common/occt/operations/advanced-loft.md index d969941c..79c53970 100644 --- a/docs/learn/code/common/occt/operations/advanced-loft.md +++ b/docs/learn/code/common/occt/operations/advanced-loft.md @@ -59,7 +59,7 @@ Here's a breakdown of the process described: {\n\n // --- 1. Camera Configuration ---\n // Create a new configuration object for the scene's Arc Rotate Camera.\n const arcCameraOptions = new CameraConfigurationDto();\n // Set the camera's position in 3D space (x, y, z).\n arcCameraOptions.position = [15, 15, 15];\n // Set the point the camera will look at.\n arcCameraOptions.lookAt = [0, 3, 0];\n // Apply these settings to the active camera in the scene.\n scene.adjustActiveArcRotateCamera(arcCameraOptions);\n\n // Define the number of corners for the polygonal profiles.\n const nrCorners = 10;\n\n // --- 2. Create the First Profile (Bottom) ---\n // Create a DTO to define an n-sided polygon wire.\n const ngonOpt = new NGonWireDto();\n ngonOpt.nrCorners = nrCorners;\n ngonOpt.radius = 3;\n // Asynchronously create the first polygon wire shape at the default center [0,0,0].\n const wire1 = await wire.createNGonWire(ngonOpt);\n\n // Create a DTO for a 2D fillet operation to round the corners of a wire.\n const filletOpt = new FilletDto();\n filletOpt.radius = 0.3; // The radius of the rounded corners.\n filletOpt.shape = wire1; // Specify that the fillet should be applied to wire1.\n // Asynchronously apply the fillet and store the new, smoothed wire.\n const filletedWire1 = await fillets.fillet2d(filletOpt);\n\n // --- 3. Create the Second Profile (Middle) ---\n // Reuse the ngonOpt DTO but modify its properties for the next shape.\n ngonOpt.center = [0, 3, 0]; // Move the center up along the Y-axis.\n ngonOpt.radius = 1; // Make this polygon smaller.\n const wire2 = await wire.createNGonWire(ngonOpt);\n\n // Reuse the filletOpt DTO to apply the same fillet radius to the new wire.\n filletOpt.shape = wire2;\n const filletedWire2 = await fillets.fillet2d(filletOpt);\n\n // --- 4. Create the Third Profile (Top) ---\n // Reuse and modify the DTOs again for the third and final profile.\n ngonOpt.center = [0, 7, 0]; // Move this one even higher.\n ngonOpt.radius = 6; // Make this polygon the largest.\n const wire3 = await wire.createNGonWire(ngonOpt);\n\n // Use a larger fillet radius for this larger wire.\n filletOpt.radius = 0.5;\n filletOpt.shape = wire3;\n const filletedWire3 = await fillets.fillet2d(filletOpt);\n\n // --- 5. Perform the Loft Operation ---\n // A loft creates a 3D solid by connecting a series of 2D profiles.\n const loftAdvancedOptions = new LoftAdvancedDto();\n // Specify a single point where the loft should begin, creating a pointed top.\n loftAdvancedOptions.startVertex = [0, 10, 0];\n // Specify a single point where the loft should end, creating a pointed bottom.\n loftAdvancedOptions.endVertex = [0, -3, 0];\n // Provide the array of profiles to connect. The order matters.\n loftAdvancedOptions.shapes = [filletedWire3, filletedWire2, filletedWire1];\n // Asynchronously execute the loft operation to create the final 3D shape.\n const loftedShape = await operations.loftAdvanced(loftAdvancedOptions)\n\n // --- 6. Draw the Final Shape ---\n // Create a DTO to define how the shape should be drawn.\n const drawOptions = new DrawOcctShapeSimpleOptions();\n // Set the color of the shape's faces to magenta.\n drawOptions.faceColour = \"#ff00ff\";\n\n // Call the generic drawing function to render the OCCT shape in the scene.\n bitbybit.draw.drawAnyAsync({\n entity: loftedShape, // The shape to draw.\n options: drawOptions // The drawing options to apply.\n });\n\n}\n\n// Execute the main function to start the script.\nstart();","version":"0.21.0","type":"typescript"}} + script={{"script":"\n// Import DTOs for configuring various operations. DTOs are objects used to pass data.\n// Import camera configuration for setting up the scene view.\nconst { CameraConfigurationDto } = Bit.Inputs.BabylonScene;\n// Import DTOs for OpenCASCADE (OCCT) geometric modeling: creating polygons, fillets, and lofts.\nconst { NGonWireDto, FilletDto, LoftAdvancedDto } = Bit.Inputs.OCCT;\n// Import DTO for specifying drawing options, like color and opacity.\nconst { DrawOcctShapeSimpleOptions } = Bit.Inputs.Draw;\n// Import a specific type for an OCCT wire, ensuring type safety in our code.\ntype TopoDSWirePointer = Bit.Inputs.OCCT.TopoDSWirePointer;\n\n// Destructure the bitbybit API to get direct access to its main modules.\n// 'scene' provides access to BabylonJS scene controls.\nconst { scene } = bitbybit.babylon;\n// 'wire', 'fillets', and 'operations' are part of the OCCT module for creating and manipulating shapes.\nconst { wire } = bitbybit.occt.shapes;\nconst { fillets, operations } = bitbybit.occt;\n\n// Define an asynchronous function to execute the main logic.\n// Using async/await is necessary because geometry creation and drawing are non-blocking operations.\nconst start = async () => {\n\n // --- 1. Camera Configuration ---\n // Create a new configuration object for the scene's Arc Rotate Camera.\n const arcCameraOptions = new CameraConfigurationDto();\n // Set the camera's position in 3D space (x, y, z).\n arcCameraOptions.position = [15, 15, 15];\n // Set the point the camera will look at.\n arcCameraOptions.lookAt = [0, 3, 0];\n // Apply these settings to the active camera in the scene.\n scene.adjustActiveArcRotateCamera(arcCameraOptions);\n\n // Define the number of corners for the polygonal profiles.\n const nrCorners = 10;\n\n // --- 2. Create the First Profile (Bottom) ---\n // Create a DTO to define an n-sided polygon wire.\n const ngonOpt = new NGonWireDto();\n ngonOpt.nrCorners = nrCorners;\n ngonOpt.radius = 3;\n // Asynchronously create the first polygon wire shape at the default center [0,0,0].\n const wire1 = await wire.createNGonWire(ngonOpt);\n\n // Create a DTO for a 2D fillet operation to round the corners of a wire.\n const filletOpt = new FilletDto();\n filletOpt.radius = 0.3; // The radius of the rounded corners.\n filletOpt.shape = wire1; // Specify that the fillet should be applied to wire1.\n // Asynchronously apply the fillet and store the new, smoothed wire.\n const filletedWire1 = await fillets.fillet2d(filletOpt);\n\n // --- 3. Create the Second Profile (Middle) ---\n // Reuse the ngonOpt DTO but modify its properties for the next shape.\n ngonOpt.center = [0, 3, 0]; // Move the center up along the Y-axis.\n ngonOpt.radius = 1; // Make this polygon smaller.\n const wire2 = await wire.createNGonWire(ngonOpt);\n\n // Reuse the filletOpt DTO to apply the same fillet radius to the new wire.\n filletOpt.shape = wire2;\n const filletedWire2 = await fillets.fillet2d(filletOpt);\n\n // --- 4. Create the Third Profile (Top) ---\n // Reuse and modify the DTOs again for the third and final profile.\n ngonOpt.center = [0, 7, 0]; // Move this one even higher.\n ngonOpt.radius = 6; // Make this polygon the largest.\n const wire3 = await wire.createNGonWire(ngonOpt);\n\n // Use a larger fillet radius for this larger wire.\n filletOpt.radius = 0.5;\n filletOpt.shape = wire3;\n const filletedWire3 = await fillets.fillet2d(filletOpt);\n\n // --- 5. Perform the Loft Operation ---\n // A loft creates a 3D solid by connecting a series of 2D profiles.\n const loftAdvancedOptions = new LoftAdvancedDto();\n // Specify a single point where the loft should begin, creating a pointed top.\n loftAdvancedOptions.startVertex = [0, 10, 0];\n // Specify a single point where the loft should end, creating a pointed bottom.\n loftAdvancedOptions.endVertex = [0, -3, 0];\n // Provide the array of profiles to connect. The order matters.\n loftAdvancedOptions.shapes = [filletedWire3, filletedWire2, filletedWire1];\n // Asynchronously execute the loft operation to create the final 3D shape.\n const loftedShape = await operations.loftAdvanced(loftAdvancedOptions)\n\n // --- 6. Draw the Final Shape ---\n // Create a DTO to define how the shape should be drawn.\n const drawOptions = new DrawOcctShapeSimpleOptions();\n // Set the color of the shape's faces to magenta.\n drawOptions.faceColour = \"#ff00ff\";\n\n // Call the generic drawing function to render the OCCT shape in the scene.\n bitbybit.draw.drawAnyAsync({\n entity: loftedShape, // The shape to draw.\n options: drawOptions // The drawing options to apply.\n });\n\n}\n\n// Execute the main function to start the script.\nstart();","version":"0.21.0","type":"typescript"}} title="Advanced Loft Operation" /> diff --git a/docs/learn/hosting-and-cdn.md b/docs/learn/hosting-and-cdn.md index 02fc9951..1e7eb039 100644 --- a/docs/learn/hosting-and-cdn.md +++ b/docs/learn/hosting-and-cdn.md @@ -66,7 +66,7 @@ Each release contains a complete set of assets packaged as a `.zip` file. Downlo An important consideration: **you probably don't need to host all assets**, only the ones your specific implementation requires. **Examples:** -- If you use `bitbybit-runner-babylonjs.js`, you won't need `bitbybit-runner-threejs.js` or the lite runners +- If you use `bitbybit-runner-babylonjs.js`, you won't need `bitbybit-runner-threejs.js`, `bitbybit-runner-playcanvas.js`, or any of the lite runners - If you only use the OCCT kernel, you'll only need to host `bitbybit-occt-webworker.js` - If you never load GLTF files with Draco compression, you don't need the Draco decompressor @@ -129,7 +129,7 @@ GlobalCDNProvider.BITBYBIT_CDN_URL = "https://cdn.yourownhosting.com/bitbybit/"; ``` -

If you're using NPM packages like @bitbybit-dev/occt, @bitbybit-dev/babylonjs, or @bitbybit-dev/threejs, you must use the GlobalCDNProvider approach. The cdnUrl option in the runner configuration only applies when using the standalone runner files.

+

If you're using NPM packages like @bitbybit-dev/occt, @bitbybit-dev/babylonjs, @bitbybit-dev/threejs, or @bitbybit-dev/playcanvas, you must use the GlobalCDNProvider approach. The cdnUrl option in the runner configuration only applies when using the standalone runner files.

### Important Considerations diff --git a/docs/learn/npm-packages/babylonjs/intro.md b/docs/learn/npm-packages/babylonjs/intro.md index 0c3c8801..1e95f82c 100644 --- a/docs/learn/npm-packages/babylonjs/intro.md +++ b/docs/learn/npm-packages/babylonjs/intro.md @@ -70,7 +70,7 @@ If you're looking to get started or deepen your knowledge of BabylonJS: * **BabylonJS Forum (Official):** forum.babylonjs.com - The central hub for community support, questions, and showcasing projects. * **Babylon 101 (Tutorials):** Babylon 101 Series - Great for beginners. * **YouTube Channel:** Official BabylonJS YouTube - Tutorials and community highlights. -* **GitHub Repository:** BabylonJS/Babylon.js - The engine's source code. +* **GitHub Repository:** BabylonJS/BabylonJS - The engine's source code. ## How Bitbybit Uses BabylonJS diff --git a/docs/learn/npm-packages/babylonjs/start-with-babylon-js.md b/docs/learn/npm-packages/babylonjs/start-with-babylon-js.md index d0a545e6..32fc38cf 100644 --- a/docs/learn/npm-packages/babylonjs/start-with-babylon-js.md +++ b/docs/learn/npm-packages/babylonjs/start-with-babylon-js.md @@ -159,7 +159,7 @@ const init = async () => { // This CDN link provides a hosted version. // For production, you might want to host this yourself. locateFile: () => { - return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@latest/wasm/manifold-3-3-2.wasm'; + return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm'; }, }); wasm.setup(); // Additional setup step for Manifold @@ -205,9 +205,10 @@ import { ArcRotateCamera, Vector3, HemisphericLight, - Color4, // Using Color4 for scene clearColor for alpha + DirectionalLight, + Color4, } from '@babylonjs/core'; -import { first, firstValueFrom, tap } from 'rxjs'; +import { first, firstValueFrom, map } from 'rxjs'; // Define an interface for kernel options interface KernelOptions { @@ -222,7 +223,6 @@ start(); async function start() { // Initialize basic BabylonJS scene const { scene, engine } = initBabylonJS(); - // Create an instance of BitByBitBase for BabylonJS const bitbybit = new BitByBitBase(); @@ -249,7 +249,8 @@ async function start() { // Start the BabylonJS render loop engine.runRenderLoop(() => { - if (scene.activeCamera) { // Ensure camera is ready + if (scene.activeCamera) { + // Ensure camera is ready scene.render(); } }); @@ -275,14 +276,25 @@ function initBabylonJS() { scene ); camera.attachControl(canvas, true); - camera.wheelPrecision = 50; // Control zoom speed + camera.wheelPrecision = 5; // Control zoom speed + camera.zoomOnFactor = 1.2; + camera.angularSensibilityX = 1000; + camera.angularSensibilityY = 1000; + camera.panningSensibility = 100; camera.lowerRadiusLimit = 10; camera.upperRadiusLimit = 500; - const light = new HemisphericLight('light', new Vector3(0, 1, 0), scene); light.intensity = 0.7; + // Add a directional light for better shadows and lighting + const directionalLight = new DirectionalLight( + 'directionalLight', + new Vector3(-1, -2, -1), + scene + ); + directionalLight.intensity = 1; + const onWindowResize = () => { engine.resize(); }; @@ -296,7 +308,7 @@ async function initWithKernels( scene: Scene, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -304,20 +316,20 @@ async function initWithKernels( // 1. Conditionally create worker instances if (options.enableOCCT) { occtWorkerInstance = new Worker( - new URL('./workers/occt.worker.ts', import.meta.url), - { name: 'OCC_WORKER', type: 'module' } + new URL('./workers/occt.worker.ts', import.meta.url), + { name: 'OCC_WORKER', type: 'module' } ); } if (options.enableJSCAD) { jscadWorkerInstance = new Worker( - new URL('./workers/jscad.worker.ts', import.meta.url), - { name: 'JSCAD_WORKER', type: 'module' } + new URL('./workers/jscad.worker.ts', import.meta.url), + { name: 'JSCAD_WORKER', type: 'module' } ); } if (options.enableManifold) { manifoldWorkerInstance = new Worker( - new URL('./workers/manifold.worker.ts', import.meta.url), - { name: 'MANIFOLD_WORKER', type: 'module' } + new URL('./workers/manifold.worker.ts', import.meta.url), + { name: 'MANIFOLD_WORKER', type: 'module' } ); } @@ -330,91 +342,89 @@ async function initWithKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { anyKernelSelectedForInit = true; if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log('OCCT Initialized')) - ) - ).then(() => {}) // Ensure the promise resolves to void for Promise.all - ); + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => 'OCCT') + ) + ) + ); } else { - console.warn( - 'OCCT enabled in options, but occtWorkerManager not found after init.' - ); + console.warn( + 'OCCT enabled in options, but occtWorkerManager not found after init.' + ); } } if (options.enableJSCAD) { anyKernelSelectedForInit = true; if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log('JSCAD Initialized')) - ) - ).then(() => {}) - ); + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => 'JSCAD') + ) + ) + ); } else { - console.warn( - 'JSCAD enabled in options, but jscadWorkerManager not found after init.' - ); + console.warn( + 'JSCAD enabled in options, but jscadWorkerManager not found after init.' + ); } } if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log('Manifold Initialized')) - ) - ).then(() => {}) - ); + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => 'Manifold') + ) + ) + ); } else { - console.warn( - 'Manifold enabled in options, but manifoldWorkerManager not found after init.' - ); + console.warn( + 'Manifold enabled in options, but manifoldWorkerManager not found after init.' + ); } } // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log('No kernels selected for initialization.'); - return { message: 'No kernels selected for initialization.' }; + return { message: 'No kernels selected for initialization.', initializedKernels: [] }; } if (initializationPromises.length === 0) { // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) console.log( - 'Kernels were selected, but none had managers available for awaiting initialization.' + 'Kernels were selected, but none had managers available for awaiting initialization.' ); return { - message: 'Selected kernels were not awaitable for initialization state.', + message: 'Selected kernels were not awaitable for initialization state.', + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log('Selected and awaitable kernels initialized:', options); + const initializedKernels = await Promise.all(initializationPromises); + console.log('Kernels initialized:', initializedKernels.join(', ')); return { - message: 'Selected and awaitable kernels initialized successfully.', + message: \`Successfully initialized: \${initializedKernels.join(', ')}\`, + initializedKernels, }; } // --- 6. Geometry Creation Functions (Examples) --- -// These functions demonstrate using Bitbybit's core geometry API. -// You use Inputs DTOs to define geometry parameters. -// bitbybit.draw.drawAnyAsync() from @bitbybit-dev/babylonjs handles rendering into the BabylonJS scene. - async function createOCCTGeometry(bitbybit: BitByBitBase, color: string) { console.log('Creating OCCT geometry...'); const cubeOptions = new Inputs.OCCT.CubeDto(); @@ -429,14 +439,17 @@ async function createOCCTGeometry(bitbybit: BitByBitBase, color: string) { const roundedCube = await bitbybit.occt.fillets.filletEdges(filletOptions); const drawOptions = new Inputs.Draw.DrawOcctShapeOptions(); - drawOptions.faceColour = color; // Bitbybit handles color conversion + drawOptions.edgeWidth = 5; + drawOptions.faceColour = color; + drawOptions.drawVertices = true; + drawOptions.vertexSize = 0.5; + drawOptions.vertexColour = '#ffffff'; await bitbybit.draw.drawAnyAsync({ entity: roundedCube, options: drawOptions, }); console.log('OCCT geometry created and drawn.'); } - async function createManifoldGeometry(bitbybit: BitByBitBase, color: string) { console.log('Creating Manifold geometry...'); const sphereOptions = new Inputs.Manifold.SphereDto(); @@ -477,9 +490,10 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { geodesicSphereOptions ); + // Example: Create another simple sphere for a boolean operation const sphereOptions = new Inputs.JSCAD.SphereDto(); - sphereOptions.radius = 10; - sphereOptions.center = [5, 45, 0]; + sphereOptions.radius = 10; // Smaller sphere + sphereOptions.center = [5, 45, 0]; // Slightly offset const simpleSphere = await bitbybit.jscad.shapes.sphere(sphereOptions); const unionOptions = new Inputs.JSCAD.BooleanTwoObjectsDto(); @@ -502,13 +516,13 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { 1. **Imports:** * `BitByBitBase` and `Inputs`: Core components from `@bitbybit-dev/babylonjs`. `Inputs` provides DTOs (Data Transfer Objects) for specifying parameters for geometry operations. * `...StateEnum`: Enums used to check the initialization state of each kernel worker. - * Standard BabylonJS modules for scene setup: `Engine`, `Scene`, `ArcRotateCamera`, `Vector3`, `HemisphericLight`, `Color4`. - * `first` from `rxjs`: Used to easily subscribe to the first emission of a kernel's state. + * Standard BabylonJS modules for scene setup: `Engine`, `Scene`, `ArcRotateCamera`, `Vector3`, `HemisphericLight`, `DirectionalLight`, `Color4`. + * `first`, `firstValueFrom`, `map` from `rxjs`: Used to subscribe to and transform the kernel state observables. 2. **`KernelOptions` Interface:** Defines the structure for selecting which kernels to initialize. 3. **`start()` function (Main Entry Point):** - * Calls `initBabylonJS()` to set up the basic BabylonJS `Engine`, `Scene`, `Camera`, and `Light`. + * Calls `initBabylonJS()` to set up the basic BabylonJS `Engine`, `Scene`, `Camera`, and `Lights`. * Creates an instance of `BitByBitBase` from the `@bitbybit-dev/babylonjs` package. * **`kernelOptions`**: This object is key. By setting `enableOCCT`, `enableJSCAD`, and `enableManifold` to `true` or `false`, you control which kernels Bitbybit attempts to initialize. This allows for optimizing load times and resource usage if not all kernels are needed. * Calls `initWithKernels()` to initialize Bitbybit with the selected kernels, passing the BabylonJS `scene`. @@ -518,21 +532,22 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { 4. **`initBabylonJS()` function:** * Standard BabylonJS boilerplate: sets up the `Engine` attached to the canvas. * Creates a `Scene` and sets its clear color (background). - * Creates an `ArcRotateCamera` for user interaction and attaches its controls to the canvas. - * Adds a `HemisphericLight` to illuminate the scene. + * Creates an `ArcRotateCamera` for user interaction with configured sensitivities and limits. + * Adds a `HemisphericLight` and a `DirectionalLight` for better lighting. * Includes a window resize listener to ensure the `engine` adapts to viewport changes. 5. **`initWithKernels()` function:** * This is the core of Bitbybit's initialization. * It conditionally creates `Worker` instances for OCCT, JSCAD, and Manifold based on the `options` passed in. The `new URL('./workers/worker-name.worker.ts', import.meta.url)` syntax is Vite's way of correctly bundling and referencing web worker files. * Calls `await bitbybit.init(scene, occtWorkerInstance, jscadWorkerInstance, manifoldWorkerInstance)`. The `@bitbybit-dev/babylonjs` package's `BitByBitBase` is designed to work with a BabylonJS `Scene` object and can handle `undefined` for worker instances it shouldn't initialize. - * It then uses RxJS `pipe(first(...))` to subscribe to the state observables of each enabled kernel (`occWorkerState$`, `jscadWorkerState$`, `manifoldWorkerState$`). + * Uses RxJS `pipe(first(...), map(...))` to subscribe to the state observables of each enabled kernel and transform the result to a string identifying the kernel. * The `Promise` resolves only after all *selected and enabled* kernels have emitted an `initialised` state. This ensures that you don't try to use a kernel before it's ready. + * Returns an object with a message and an array of initialized kernel names. 6. **Geometry Creation Functions (`createOCCTGeometry`, `createManifoldGeometry`, `createJSCADGeometry`):** * These are example functions illustrating how to use Bitbybit's core geometry API (e.g., `bitbybit.occt.*`, `bitbybit.manifold.*`, `bitbybit.jscad.*`). * **`Inputs` DTOs**: You'll notice the use of `Inputs.OCCT.CubeDto()`, `Inputs.Manifold.SphereDto()`, etc. These objects are used to pass parameters to Bitbybit's geometry creation and modification functions. They provide type safety and often mirror the inputs you'd find in a visual programming environment. Intellisense (auto-completion in your IDE) will be very helpful here. - * **Drawing**: After creating a geometric entity, `bitbybit.draw.drawAnyAsync()` (from `@bitbybit-dev/babylonjs`) is used to render it into the BabylonJS scene. Different kernels might have slightly different drawing option DTOs (e.g., `DrawOcctShapeOptions`, `DrawManifoldOrCrossSectionOptions`, `DrawBasicGeometryOptions`), but `drawAnyAsync` handles many common cases. + * **Drawing**: After creating a geometric entity, `bitbybit.draw.drawAnyAsync()` (from `@bitbybit-dev/babylonjs`) is used to render it into the BabylonJS scene. Different kernels might have slightly different drawing option DTOs (e.g., `DrawOcctShapeOptions`, `DrawManifoldOrCrossSectionOptions`, `DrawBasicGeometryOptions`), but `drawAnyAsync` handles many common cases. The OCCT draw options now support additional properties like `edgeWidth`, `drawVertices`, `vertexSize`, and `vertexColour`. ## 5. Basic Styling (Optional) diff --git a/docs/learn/npm-packages/game-engine-agnostic.md b/docs/learn/npm-packages/game-engine-agnostic.md index 0b3b6c03..ab5d74e7 100644 --- a/docs/learn/npm-packages/game-engine-agnostic.md +++ b/docs/learn/npm-packages/game-engine-agnostic.md @@ -2,8 +2,8 @@ sidebar_position: 2 title: "Bitbybit's Engine-Agnostic Architecture" sidebar_label: "Engine Agnosticism" -description: "Learn about Bitbybit's core architecture, designed to be game engine agnostic, its base layer of fundamental algorithms, and its current integrations with ThreeJS and BabylonJS." -tags: [threejs, babylonjs, npm-packages] +description: "Learn about Bitbybit's core architecture, designed to be game engine agnostic, its base layer of fundamental algorithms, and its current integrations with ThreeJS, BabylonJS, and PlayCanvas." +tags: [threejs, babylonjs, playcanvas, npm-packages] --- import Admonition from '@theme/Admonition'; @@ -52,7 +52,7 @@ Bitbybit's architecture can be visualized as a layered system: ## Current Rendering Engine Integrations -As of now, Bitbybit provides official integration layers for two of the most popular and powerful WebGL-based 3D engines: +As of now, Bitbybit provides official integration layers for three of the most popular and powerful WebGL-based 3D engines: ### 1. [ThreeJS](https://threejs.org/) Integration ([`@bitbybit-dev/threejs`](https://www.npmjs.com/package/@bitbybit-dev/threejs)) @@ -70,9 +70,16 @@ As of now, Bitbybit provides official integration layers for two of the most pop * **Bitbybit Integration:** Our `@bitbybit-dev/babylonjs` NPM package serves the same purpose as the ThreeJS integration but targets the BabylonJS engine. Its implementation of `drawAnyAsync` translates Bitbybit's core geometric data into `BABYLON.Mesh` objects and integrates them into a BabylonJS scene. * **Use Cases:** A great choice for projects requiring a rich set of out-of-the-box features, physics integration, advanced rendering effects, or for developers who appreciate the stability and robust tooling offered by a TypeScript-first engine with strong corporate backing and community support. +### 3. [PlayCanvas](https://playcanvas.com/) Integration ([`@bitbybit-dev/playcanvas`](https://www.npmjs.com/package/@bitbybit-dev/playcanvas)) + +* **Overview:** PlayCanvas is a high-performance WebGL game engine known for its exceptional **mobile optimization** and **cloud-based visual editor**. It uses an entity-component architecture and is built for speed, making it ideal for creating performant 3D applications that need to run smoothly across all devices. +* **Community and Ecosystem:** PlayCanvas has a dedicated community of game developers and 3D creators. The engine is **open source** (MIT licensed) with strong commercial backing. Its focus on performance and professional game development has made it popular for web-based games, product configurators, and interactive experiences. +* **Bitbybit Integration:** Our `@bitbybit-dev/playcanvas` NPM package provides the necessary tools, including its version of `drawAnyAsync`, to take the geometric output from Bitbybit's core and render it efficiently within a PlayCanvas application. It handles the creation of PlayCanvas entities and mesh instances from the kernel-generated data. +* **Use Cases:** Ideal for high-performance applications, mobile-first experiences, game development, product configurators requiring smooth performance across devices, or projects where runtime efficiency and small bundle sizes are critical. + ## Using the Integrations via NPM Packages -When you use Bitbybit through our [NPM packages](/learn/npm-packages/intro) in your own JavaScript/TypeScript projects, you'll choose one of these integration layers. The `BitByBitBase` class from the respective package (`@bitbybit-dev/threejs` or `@bitbybit-dev/babylonjs`) is initialized with your chosen engine's scene object. From there, functions like `bitbybit.draw.drawAnyAsync()` will automatically use the correct engine-specific logic to create and render meshes. +When you use Bitbybit through our [NPM packages](/learn/npm-packages/intro) in your own JavaScript/TypeScript projects, you'll choose one of these integration layers. The `BitByBitBase` class from the respective package (`@bitbybit-dev/threejs`, `@bitbybit-dev/babylonjs`, or `@bitbybit-dev/playcanvas`) is initialized with your chosen engine's scene object. From there, functions like `bitbybit.draw.drawAnyAsync()` will automatically use the correct engine-specific logic to create and render meshes. Our visual editors (Rete, Blockly) and the Monaco TypeScript editor on the Bitbybit platform also utilize these integration layers, but they primarily use BabylonJS game engine for rendering under the hood to provide you with a seamless 3D modeling experience. diff --git a/docs/learn/npm-packages/intro.md b/docs/learn/npm-packages/intro.md index aa2edb99..fff22961 100644 --- a/docs/learn/npm-packages/intro.md +++ b/docs/learn/npm-packages/intro.md @@ -3,7 +3,7 @@ sidebar_position: 1 title: Our NPM Packages sidebar_label: Intro description: Discover the open-source NPM packages that power Bitbybit, allowing you to integrate our 3D CAD algorithms into your own applications. -tags: [npm-packages, threejs, babylonjs, occt, manifold, jscad] +tags: [npm-packages, threejs, babylonjs, playcanvas, occt, manifold, jscad] --- # Our NPM Packages @@ -12,7 +12,7 @@ When developing standalone applications in TypeScript or JavaScript, you often u ## Architecture of NPM Packages -The following diagram illustrates the architecture of our NPM packages. Each game engine-specific package (`@bitbybit-dev/threejs`, `@bitbybit-dev/babylonjs`) connects through the `@bitbybit-dev/core` layer. This creates a streamlined, extensible structure that supports a range of integrations with various geometry kernels like OCCT, JSCAD, and Manifold. +The following diagram illustrates the architecture of our NPM packages. Each game engine-specific package (`@bitbybit-dev/threejs`, `@bitbybit-dev/babylonjs`, `@bitbybit-dev/playcanvas`) connects through the `@bitbybit-dev/core` layer. This creates a streamlined, extensible structure that supports a range of integrations with various geometry kernels like OCCT, JSCAD, and Manifold. ![Architecture of Bitbybit NPM packages](https://ik.imagekit.io/bitbybit/app/assets/npm-package-architecture.jpeg) *Architecture of Bitbybit NPM packages* @@ -41,6 +41,13 @@ Allows users of the [BabylonJS](https://babylonjs.com) game engine to use our co Enables users of the [ThreeJS](https://threejs.org) library to utilize our core 3D CAD algorithms. Similar to the BabylonJS package, it assists in building 3D applications by providing algorithms for constructing meshes from OpenCascade or JSCAD geometry. +### @bitbybit-dev/playcanvas + +- [NPM Package](https://www.npmjs.com/package/@bitbybit-dev/playcanvas) +- [GitHub Source](https://github.com/bitbybit-dev/bitbybit/tree/master/packages/dev/playcanvas) + +Enables users of the [PlayCanvas](https://playcanvas.com) game engine to utilize our core 3D CAD algorithms. It assists in building high-performance 3D applications by providing algorithms for constructing meshes and entities from OpenCascade, JSCAD, or Manifold geometry kernels. + --- ## Core & Kernel Packages diff --git a/docs/learn/npm-packages/playcanvas/_category_.json b/docs/learn/npm-packages/playcanvas/_category_.json new file mode 100644 index 00000000..35dd78c4 --- /dev/null +++ b/docs/learn/npm-packages/playcanvas/_category_.json @@ -0,0 +1,10 @@ +{ + "label": "PlayCanvas", + "position": 5, + "link": { + "type": "generated-index", + "title": "Integrate Bitbybit with PlayCanvas", + "description": "Learn how to integrate Bitbybit with PlayCanvas to create interactive 3D experiences, CAD apps, 3D configurators or other procedural web applications.", + "slug": "/npm-packages/playcanvas" + } +} diff --git a/docs/learn/npm-packages/playcanvas/advanced-parametric-3d-model.md b/docs/learn/npm-packages/playcanvas/advanced-parametric-3d-model.md new file mode 100644 index 00000000..d4999289 --- /dev/null +++ b/docs/learn/npm-packages/playcanvas/advanced-parametric-3d-model.md @@ -0,0 +1,369 @@ +--- +sidebar_position: 3 +title: "Advanced Parametric 3D Model with PlayCanvas & Bitbybit" +sidebar_label: "Parametric Model (PlayCanvas)" +description: "Learn how to build an advanced, interactive parametric 3D model using Bitbybit with PlayCanvas, OCCT, and a GUI for real-time control." +tags: [npm-packages, playcanvas] +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import Admonition from '@theme/Admonition'; + +# Advanced Parametric 3D Model with PlayCanvas & Bitbybit + +This tutorial explores a more advanced example of creating an interactive, parametric 3D model using Bitbybit's PlayCanvas integration. We'll build a configurable 3D shape whose geometry is driven by parameters controlled via a GUI (Graphical User Interface), leveraging the OpenCascade (OCCT) kernel for robust CAD operations. + + + The video tutorial below demonstrates a similar parametric model using ThreeJS, not PlayCanvas. The core concepts, OCCT operations, and workflow are identical across both engines. This PlayCanvas tutorial follows the same architectural approach adapted for PlayCanvas's entity-component system. + + +You can see what the results of this app look like (rendered in Unreal Engine): +
+ +
+ +This example demonstrates: +* Setting up a PlayCanvas application. +* Initializing Bitbybit with specific geometry kernels (OCCT in this case). +* Creating parametric geometry using Bitbybit's OCCT API. +* Using `lil-gui` to create a simple UI for controlling model parameters. +* Dynamically updating the 3D model in response to UI changes. +* Implementing Level of Detail (LOD) for shape generation (a simpler version for quick updates, a more detailed one for finalization). +* Handling 3D model exports (STEP). + +We are providing a higher level explanations of the codebase below, but for working reference always check this live example on StackBlitz, which, as platform evolves could change slightly. + + + +### Find the source code on [Bitbybit GitHub Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/vite/playcanvas/hex-shell) + + +## Project Structure Overview + +This project is typically structured with: +* `index.html`: The main HTML file to host the canvas and load scripts. +* `style.css`: For basic styling of the page and UI elements like a loading spinner. +* `src/main.ts`: The main entry point of our application, orchestrating app setup, Bitbybit initialization, GUI, and geometry updates. +* `src/models/`: A directory to define data structures for our model parameters (`model.ts`), kernel initialization options (`kernel-options.ts`), and current scene state (`current.ts`). +* `src/helpers/`: A directory for utility functions, broken down by responsibility: + * `init-playcanvas.ts`: Sets up the PlayCanvas application, camera, lighting, and ground. + * `init-kernels.ts`: Handles the initialization of selected Bitbybit geometry kernels. + * `create-shape.ts`: Contains the core logic for generating the parametric 3D geometry using OCCT. This is where the detailed CAD operations happen. + * `create-gui.ts`: Sets up the `lil-gui` panel and links its controls to the model parameters and update functions. + * `downloads.ts`: Implements functions for exporting the model to various file formats. + * `gui-helper.ts`: Provides utility functions for managing the GUI state (e.g., showing/hiding a spinner, disabling/enabling GUI). +* `src/workers/`: Directory containing the individual worker files for each geometry kernel (e.g., `occt.worker.ts`). + + + For a detailed explanation of how to set up the Web Worker files (`occt.worker.ts`, `jscad.worker.ts`, `manifold.worker.ts`), please refer to our [**PlayCanvas Integration Starter Tutorial**](./start-with-playcanvas). This current tutorial focuses on the application logic built upon that foundation. + + +## 1. HTML Setup (`index.html`) + +The HTML file is straightforward, providing the basic structure for our 3D application. + + +{` + + + + + + Bitbybit & PlayCanvas Hex Shell Example + + + + + + +`} + + +**Key elements:** +* A `` element where the PlayCanvas application will be rendered. +* A script tag to load our main application logic from `src/main.ts`. +* A simple Bitbybit logo link. + +## 2. Main Application Logic (`src/main.ts`) + +This is the heart of our application, orchestrating all the major components. + + +{`import './style.css'; +import { BitByBitBase, Inputs } from '@bitbybit-dev/playcanvas'; +import { model, type KernelOptions, current } from './models'; +import { + initKernels, + initPlayCanvas, + createGui, + createShapeLod1, + createShapeLod2, + createLightsAndGround, + disableGUI, + enableGUI, + hideSpinner, + showSpinner, + downloadStep, +} from './helpers'; + +// Configure which geometry kernels to enable +const kernelOptions: KernelOptions = { + enableOCCT: true, // We'll use OCCT for this parametric model + enableJSCAD: false, + enableManifold: false, +}; + +// Start the application +start(); + +async function start() { + // 1. Initialize the PlayCanvas application, camera, and basic lighting/ground + const { app } = initPlayCanvas(); + createLightsAndGround(app, current); // 'current' stores references to app objects + + // 2. Initialize Bitbybit with the PlayCanvas app and selected kernels + const bitbybit = new BitByBitBase(); + await initKernels(app, bitbybit, kernelOptions); + + // Variables to hold the OCCT shape representation and shapes to clean up + let finalShape: Inputs.OCCT.TopoDSShapePointer | undefined; + let shapesToClean: Inputs.OCCT.TopoDSShapePointer[] = []; // Important for memory management + + // 3. Connect download functions to the model object (used by GUI) + model.downloadStep = () => downloadStep(bitbybit, finalShape); + + // 4. Create the GUI panel and link it to model parameters and the updateShape function + createGui(current, model, updateShape); + + // 5. Basic animation setup for rotating the model + const rotationSpeed = 0.0005; + const rotateEntities = () => { + if ( + model.rotationEnabled && + current.entity1 && // Assumes entity1, entity2, dimensions are populated by createShape... + current.entity2 && + current.dimensions + ) { + current.entity1.rotate(0, rotationSpeed * 180 / Math.PI, 0); + current.entity2.rotate(0, rotationSpeed * 180 / Math.PI, 0); + current.dimensions.rotate(0, rotationSpeed * 180 / Math.PI, 0); + } + }; + + // Hook into PlayCanvas update loop for animation + app.on('update', () => { + rotateEntities(); + }); + + // 6. Initial shape creation (Level of Detail 1 - faster preview) + finalShape = await createShapeLod1( + bitbybit, + app, + model, // Current model parameters + shapesToClean, // Array to track OCCT shapes for later cleanup + current // Object to store references to current PlayCanvas entities + ); + + // 7. Function to update the shape when GUI parameters change + async function updateShape(finish: boolean) { + disableGUI(); // Prevent further interaction during update + showSpinner(); // Indicate processing + + // Remove previous PlayCanvas entities from the app + current.entity1?.destroy(); + current.entity2?.destroy(); + current.dimensions?.destroy(); + // Note: OCCT shapes are cleaned up within createShapeLod1/2 via shapesToClean + + if (finish) { // 'finish' is true when "Finalize" button in GUI is clicked + finalShape = await createShapeLod2( // Higher detail + bitbybit, app, model, shapesToClean, current + ); + } else { // Default update (e.g., from slider drag) + finalShape = await createShapeLod1( // Lower detail for speed + bitbybit, app, model, shapesToClean, current + ); + } + + hideSpinner(); + enableGUI(); // Re-enable GUI + } +} +`} + + +**Explanation of `main.ts`:** + +1. **Imports:** Pulls in necessary Bitbybit modules, data models, and helper functions. +2. **`kernelOptions`:** Configures which Bitbybit geometry kernels (OCCT, JSCAD, Manifold) will be initialized. For this example, only OCCT is enabled as it's used for the parametric modeling. +3. **`start()` function:** The main asynchronous function that orchestrates the application. + * **`initPlayCanvas()` & `createLightsAndGround()`:** Sets up the basic PlayCanvas environment. + * **`BitByBitBase` & `initKernels()`:** Initializes the Bitbybit library, linking it to the PlayCanvas app and loading the configured OCCT kernel worker. + * **`finalShape` & `shapesToClean`:** `finalShape` will hold a reference to the main OCCT geometry. `shapesToClean` is crucial for managing memory in OCCT by keeping track of intermediate shapes that need to be explicitly deleted after they are no longer needed. + * **Download Functions:** Attaches download helper functions to the `model` object. These will be triggered by buttons in the GUI. + * **`createGui()`:** Initializes the `lil-gui` panel, connecting its controls to the properties defined in `model.ts` and providing the `updateShape` function as a callback when parameters change. + * **Rotation Logic:** Sets up a simple animation to rotate the generated 3D entities if `model.rotationEnabled` is true. + * **Initial Shape Creation:** Calls `createShapeLod1` to generate and draw the initial 3D model with a lower level of detail for faster startup. + * **`updateShape(finish: boolean)` function:** + * This function is called by the GUI when a parameter changes. + * It disables the GUI and shows a spinner to indicate processing. + * It removes (destroys) the previously rendered PlayCanvas entities (`current.entity1`, `current.entity2`, `current.dimensions`). + * Crucially, the `createShapeLod1` and `createShapeLod2` functions are responsible for cleaning up OCCT shapes using the `shapesToClean` array. + * It then calls either `createShapeLod1` (for quick updates, e.g., during slider dragging) or `createShapeLod2` (for a more detailed final version when a "Finalize" button is clicked). + * Finally, it hides the spinner and re-enables the GUI. + +## 3. Helper Functions (`src/helpers/`) + +The `helpers` directory modularizes different aspects of the application. + +### `init-playcanvas.ts` & `init-kernels.ts` + +* **`initPlayCanvas()`:** Contains PlayCanvas setup for the application, camera, lighting (directional and ambient), a ground plane, and camera controls. It also starts the application. +* **`createLightsAndGround()`:** A helper to specifically add directional lights (for shadows) and a ground plane to the app. +* **`initKernels()`:** This function is responsible for: + 1. Conditionally creating Web Worker instances for each kernel specified in `kernelOptions`. + 2. Calling `bitbybit.init(...)` to link Bitbybit with the PlayCanvas app and these worker instances. + 3. Asynchronously waiting for each selected and available kernel to report that it has been fully initialized before resolving. This ensures kernels are ready before use. + + +{`// ... imports ... +export async function initKernels( + app: pc.Application, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string }> { + // 1. Conditionally create worker instances based on options + // (e.g., new Worker(new URL('../workers/occt.worker.ts', import.meta.url), ...)) + // 2. Initialize Bitbybit with app and worker instances + // await bitbybit.init(app, occtWorker, jscadWorker, manifoldWorker); + // 3. Collect and await promises for kernel initializations + // (e.g., using firstValueFrom on bitbybit.occtWorkerManager.occWorkerState$) + // 4. Resolve once selected kernels are ready + return { message: "Kernels initialized" }; +}`} + + +### `create-shape.ts` (Core Geometry Logic) + +This is the most complex file, containing the specific OCCT operations to generate the parametric shape. It typically includes: + +* Functions like `createShapeLod1` (Level of Detail 1 - faster, less detailed) and `createShapeLod2` (Level of Detail 2 - slower, more detailed). +* **Memory Management:** Before creating new OCCT geometry, it calls `bitbybit.occt.deleteShapes({ shapes: shapesToClean })` to free memory used by previous intermediate OCCT shapes. New intermediate shapes created are added to `shapesToClean`. +* **Geometric Operations:** Uses various functions from `bitbybit.occt.shapes`, `bitbybit.occt.operations`, `bitbybit.occt.transforms`, etc., to: + * Create primitive wires (e.g., ellipses using `wire.createEllipseWire`). + * Transform these wires (rotate, translate). + * Loft surfaces between wires (`operations.loft`). + * Offset faces (`operations.offset`). + * Subdivide faces into patterns (e.g., `face.subdivideToHexagonWires`). + * Create solids from these operations. + * Create compound shapes. +* **Dimensioning (Optional):** The example includes logic to create OCCT dimension entities (`dimensions.simpleLinearLengthDimension`, `dimensions.simpleAngularDimension`) which are then also drawn. +* **Drawing:** + * It uses `bitbybit.draw.drawAnyAsync({ entity: occtShape, options: drawOptions })` to convert the final OCCT shapes into PlayCanvas entities and add them to the app. + * It often creates separate PlayCanvas entities for different parts of the model (e.g., `current.entity1`, `current.entity2`) for easier management and independent animation. + * Materials are created and applied to the entities. + + + The specific OCCT functions used (like `loft`, `offset`, `subdivideToHexagonWires`, `makeCompound`) are powerful CAD operations. Understanding their parameters and behavior is key to creating complex parametric models with OCCT. Refer to the Bitbybit API documentation for details on each. + + +### `create-gui.ts` + +This file uses the `lil-gui` library to create a user interface panel. + + +{`import GUI from 'lil-gui'; +// ... other imports ... +export const createGui = ( + current: Current, + model: Model, + updateShape: (finish: boolean) => void +) => { + model.update = () => updateShape(true); // Link "Finalize" button to LOD2 update + const gui = new GUI(); + current.gui = gui; // Store reference to GUI + + // Add controls for each parameter in the 'model' object + gui.add(model, 'uHex', 1, 14, 1).name('Hexagons U').onFinishChange(() => updateShape(false)); + // ... more gui.add() calls for vHex, height, colors, etc. ... + // .onFinishChange(() => updateShape(false)) calls LOD1 update for sliders + // .onChange(...) for color pickers to update material colors directly + + gui.add(model, 'update').name('Finalize'); // Button to trigger LOD2 update + gui.add(model, 'downloadSTEP').name('Download STEP'); +};`} + + +* It creates a new `GUI` instance. +* For each parameter in the `model` object (defined in `models/model.ts`), it adds a corresponding control (slider, color picker, checkbox). +* `onFinishChange` (for sliders) or `onChange` (for continuous updates like color pickers) callbacks are used to: + * Update the `model` object with the new parameter value. + * Call the `updateShape(false)` function (from `main.ts`) to regenerate the geometry with LOD1 (quick preview). +* A "Finalize" button calls `updateShape(true)` to generate the high-detail LOD2 version. +* Buttons are added to trigger the download functions. + +### `downloads.ts` + +Contains functions to export the generated 3D model: +* `downloadStep()`: Uses `bitbybit.occt.io.saveShapeSTEP()` to save the `finalShape` (the OCCT compound) as a STEP file. It includes a mirroring transformation, which might be necessary due to coordinate system differences. + +### `gui-helper.ts` + +Simple utility functions to manage the UI during processing: +* `disableGUI()` / `enableGUI()`: Make the `lil-gui` panel non-interactive and visually dimmed during updates. +* `showSpinner()` / `hideSpinner()`: Display or hide a simple CSS-based loading spinner overlay. + +## 4. Data Models (`src/models/`) + +* **`current.ts`:** Defines a `Current` type and an instance to hold references to currently active PlayCanvas entities (like entities for different model parts, lights, ground) and the `lil-gui` instance. This helps in easily accessing and manipulating these objects from different parts of the code. +* **`kernel-options.ts`:** Defines the `KernelOptions` interface used in `main.ts` to specify which geometry kernels (OCCT, JSCAD, Manifold) should be initialized by Bitbybit. +* **`model.ts`:** Defines the `Model` type and a default `model` object. This object holds all the parameters that control the geometry of the 3D shape (e.g., `uHex`, `vHex`, `height`, colors, precision). The `lil-gui` directly manipulates this object. It also includes optional function signatures for `update` and download methods, which are later assigned in `main.ts` and `create-gui.ts`. + +## 5. Styles (`style.css`) + +The `style.css` file provides basic styling: +* Resets body margin and sets a background color. +* Styles for the Bitbybit logo link. +* CSS for the `lds-ellipsis` loading spinner animation. + +## Conclusion + +This advanced example showcases a more complete workflow for creating parametric and interactive 3D applications with Bitbybit and PlayCanvas. Key takeaways include: + +* **Modular Code Structure:** Separating concerns into helper functions and data models makes the project more manageable. +* **Parametric Control:** Using a data model (`model.ts`) and a GUI (`lil-gui`) to drive geometry changes. +* **Level of Detail (LOD):** Implementing different detail levels for shape generation (`createShapeLod1` vs. `createShapeLod2`) can significantly improve performance during interactive adjustments. +* **OCCT Memory Management:** The practice of tracking and deleting intermediate OCCT shapes (`shapesToClean`) is crucial for preventing memory leaks in complex CAD operations. +* **Kernel Initialization:** Selectively initializing only the necessary geometry kernels. +* **Export Functionality:** Integrating common 3D file export options. +* **Entity-Based System:** PlayCanvas uses entities with components, and Bitbybit creates and manages these entities for rendering. + +By understanding these components and their interactions, you can build sophisticated and highly configurable 3D experiences on the web with PlayCanvas. diff --git a/docs/learn/npm-packages/playcanvas/hex-house-concept.md b/docs/learn/npm-packages/playcanvas/hex-house-concept.md new file mode 100644 index 00000000..7cc83b38 --- /dev/null +++ b/docs/learn/npm-packages/playcanvas/hex-house-concept.md @@ -0,0 +1,566 @@ +--- +sidebar_position: 4 +title: "Parametric Hex House Concept with PlayCanvas & Bitbybit" +sidebar_label: "Hex House (PlayCanvas)" +description: "Explore how to construct a parametric 'Hex House' architectural concept using Bitbybit with PlayCanvas, OCCT, and a dynamic GUI for customization." +tags: [npm-packages, playcanvas] +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; +import Admonition from '@theme/Admonition'; + +# Parametric Hex House Concept with PlayCanvas & Bitbybit + +This tutorial guides you through creating a "Hex House," an architectural concept featuring a distinctive hexagonal shell structure. We'll use Bitbybit's PlayCanvas integration, leveraging the OpenCascade (OCCT) kernel for advanced CAD operations, and `lil-gui` for a user interface that allows real-time parameter adjustments. + + + The video tutorial below demonstrates the Hex House concept using ThreeJS, not PlayCanvas. The geometric operations, parametric design techniques, and OCCT kernel usage are the same across both engines. This PlayCanvas documentation adapts the same concepts for PlayCanvas's rendering architecture. + + +In this related video tutorial you can see how the results of this app look like (rendered in Unreal Engine). +
+ +
+ + +

+ While the original tutorial was based on a somewhat outdated app structure, we've provided a more modern and well-organized version here. You can also use this scaffold as a starting point for your own projects. +

+
+ +This example will demonstrate how to: + +* Set up a PlayCanvas environment for 3D rendering. +* Initialize Bitbybit with the OCCT geometry kernel within this PlayCanvas context. +* Construct complex parametric geometry using Bitbybit's OCCT API, focusing on techniques like lofting, surface subdivision into hexagonal patterns, and creating compound shapes. +* Create a GUI with `lil-gui` to control the Hex House's parameters. +* Dynamically update the 3D model in the PlayCanvas app based on these GUI inputs. +* Manage and export the generated 3D model. + + +

This tutorial focuses on the core application logic for generating the Hex House with PlayCanvas. For a detailed explanation of:

+ +

Here, we'll concentrate on the essential files and logic that bring the Hex House concept to life: main.ts, create-gui.ts, and particularly create-shape.ts.

+
+ +We are providing a higher level explanations of the codebase below, but for working reference always check this live example on StackBlitz, which, as platform evolves could change slightly. + + + +### Find the source code on [Bitbybit GitHub Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/vite/playcanvas/hex-house-concept) + + +## 1. HTML Foundation (`index.html`) + +The `index.html` file is the standard entry point, providing a canvas for PlayCanvas. + + +{` + + + + + + Bitbybit & PlayCanvas Hex House Concept Demo + + + + {/* Canvas for PlayCanvas rendering */} + + +`} + + +* Key element: `` for PlayCanvas rendering. + +## 2. Main Application Orchestration (`src/main.ts`) + +This file coordinates the setup and dynamic updates of our Hex House within the PlayCanvas environment. + + +{`import './style.css'; +import { BitByBitBase, Inputs } from '@bitbybit-dev/playcanvas'; // PlayCanvas integration +import { model, type KernelOptions, current } from './models'; +import { + initKernels, initPlayCanvas, createGui, createShape, + createLightsAndGround, disableGUI, enableGUI, hideSpinner, showSpinner, + downloadSTEP +} from './helpers'; +import * as pc from 'playcanvas'; + +// Configure which geometry kernels to enable +const kernelOptions: KernelOptions = { + enableOCCT: true, // This example relies heavily on OCCT for its CAD operations + enableJSCAD: false, + enableManifold: false, +}; + +// Application entry point +start(); + +async function start() { + // 1. Initialize the PlayCanvas application, camera, and lighting + const { app } = initPlayCanvas(); // From helpers/init-playcanvas.ts + // Add default lighting and a ground plane + createLightsAndGround(app, current); // From helpers/init-playcanvas.ts + + // 2. Initialize Bitbybit, linking it to the PlayCanvas app and selected kernels + const bitbybit = new BitByBitBase(); + await initKernels(app, bitbybit, kernelOptions); // From helpers/init-kernels.ts + + // Variables to hold the OCCT shape representation and shapes to clean up + let finalShape: Inputs.OCCT.TopoDSShapePointer | undefined; + let shapesToClean: Inputs.OCCT.TopoDSShapePointer[] = []; // For OCCT memory management + + // 3. Connect download functions to the model object (used by GUI) + model.downloadStep = () => downloadSTEP(bitbybit, finalShape); + + // 4. Create the GUI panel and link it to model parameters and the updateShape function + createGui(current, model, updateShape); // From helpers/create-gui.ts + + // 5. Basic animation setup for rotating the model entities + const rotationSpeed = 0.0005; + const rotateEntities = () => { + if (model.rotationEnabled && current.entities && current.entities.length > 0) { + current.entities.forEach((entity) => { + if (entity) entity.rotate(0, rotationSpeed * 180 / Math.PI, 0); // Rotate each entity + }); + } + }; + + // Hook into PlayCanvas update loop for animation + app.on('update', () => { + rotateEntities(); + }); + + // 6. Initial shape creation + finalShape = await createShape( // From helpers/create-shape.ts + bitbybit, + app, + model, // Current model parameters from models/model.ts + shapesToClean, // Array to track OCCT shapes for cleanup + current // Object to store references to current PlayCanvas entities + ); + + // 7. Function to update the shape when GUI parameters change + async function updateShape() { + disableGUI(); // Prevent further interaction during update + showSpinner(); // Indicate processing + + // Destroy previous PlayCanvas entities + current.entities?.forEach((entity) => { + entity.destroy(); // PlayCanvas method to remove entity and children + }); + current.entities = []; // Reset the entities array + + // Re-create the shape with new parameters + // The createShape function handles OCCT cleanup via shapesToClean + finalShape = await createShape( + bitbybit, app, model, shapesToClean, current + ); + + hideSpinner(); + enableGUI(); // Re-enable GUI + } +}`} + + +**Core Logic in `main.ts`:** +1. Initializes the PlayCanvas application using `initPlayCanvas()` and adds lighting/ground via `createLightsAndGround()`. +2. Initializes `BitByBitBase` for PlayCanvas and then the OCCT kernel using `initKernels()`. +3. Sets up download functions and the `lil-gui` interface through `createGui()`. Changes in the GUI trigger `updateShape`. +4. A simple `rotateEntities` animation is tied to PlayCanvas's update loop. +5. The `updateShape` function is central to interactivity: + * It destroys previous PlayCanvas entities using the `destroy()` method to clear old geometry. + * It then calls `createShape` again with the potentially modified `model` parameters. The `createShape` function itself is responsible for managing the cleanup of intermediate OCCT shapes using the `shapesToClean` array. + +## 3. Essential Helper Functions (`src/helpers/`) + +We'll focus on the provided `create-gui.ts` and `create-shape.ts`. For `init-playcanvas.ts` and `init-kernels.ts`, their roles are analogous to those described in the "Advanced Parametric Model (PlayCanvas)" tutorial (setting up the PlayCanvas environment and initializing Bitbybit kernels, respectively). + +### Creating the GUI (`create-gui.ts`) + +This file uses `lil-gui` to build the user interface for controlling the Hex House parameters. + + +{`import GUI from 'lil-gui'; +import type { Current, Model } from '../models'; +import * as pc from 'playcanvas'; + +export const createGui = ( + current: Current, + model: Model, + updateShape: () => void +) => { + const gui = new GUI(); + current.gui = gui; // Store reference to the GUI instance + gui.$title.innerHTML = 'Patterns'; // descriptive title + + // Add controls for uHex, vHex (number of hexagons) + gui.add(model, 'uHex', 5, 81, 4).name('Hexagons U') + .onFinishChange((value: number) => { model.uHex = value; updateShape(); }); + gui.add(model, 'vHex', 5, 12, 1).name('Hexagons V') + .onFinishChange((value: number) => { model.vHex = value; updateShape(); }); + + // Controls for drawing edges and faces + gui.add(model, 'drawEdges').name('Draw Edges').onFinishChange(() => updateShape()); + gui.add(model, 'drawFaces').name('Draw Faces').onFinishChange(() => updateShape()); + + // Color control for the main shell material + gui.addColor(model, 'color').name('Shell Color') + .onChange((hexColor: string) => { + if (current.entities && current.entities.length > 0) { + // Find and update materials in the first entity + const firstEntity = current.entities[0]; + if (firstEntity) { + firstEntity.findComponents('render').forEach(renderComponent => { + if (renderComponent.material) { + const color = new pc.Color().fromString(hexColor); + renderComponent.material.diffuse.copy(color); + renderComponent.material.update(); + } + }); + } + } + }); + + gui.add(model, 'downloadStep').name('Download STEP'); +};`} + + +**`create-gui.ts` functionality:** +* Initializes a `lil-gui` panel. +* Adds controls (sliders for `uHex`, `vHex`; checkboxes for `drawEdges`, `drawFaces`; a color picker for `color`). +* Each control's `onFinishChange` (for sliders/checkboxes) or `onChange` (for the color picker) callback: + 1. Updates the corresponding property in the `model` object. + 2. Calls the `updateShape` function (passed from `main.ts`) to trigger a regeneration of the geometry. +* For color changes, it iterates through render components in the entities and updates their material diffuse color using PlayCanvas's color system. +* Adds buttons to trigger download functions (defined in `main.ts` and `helpers/downloads.ts`). + +### Generating the Hex House Geometry (`create-shape.ts`) + +This is the heart of the parametric model, where complex CAD operations using Bitbybit's OCCT API define the "Hex House" structure. + + +{`import type { BitByBitBase } from '@bitbybit-dev/playcanvas'; +import { Inputs } from '@bitbybit-dev/playcanvas'; +import * as pc from 'playcanvas'; +import type { Current, Model } from '../models'; + +// Main function to create the entire Hex House shape +export const createShape = async ( + bitbybit: BitByBitBase | undefined, + app: pc.Application | undefined, + model: Model, // Contains parameters from the GUI (uHex, vHex, color, etc.) + shapesToClean: Inputs.OCCT.TopoDSShapePointer[], // Array to manage OCCT memory + current: Current // Stores references to current PlayCanvas entities +) => { + if (app && bitbybit) { + // 1. OCCT Memory Management: Clean up shapes from the previous generation + if (shapesToClean.length > 0) { + await bitbybit.occt.deleteShapes({ shapes: shapesToClean }); + } + + shapesToClean = []; // Reset the array for the new generation + + type Point3 = Inputs.Base.Point3; // Alias for convenience + + // Define sets of points that will guide the creation of NURBS curves + const sd = { // sd stands for 'shape data' + groundCrv: [ [-15, 0.1, -4.5], [0, 0.1, -3.5], [13, 0.1, -4.5], ] as Point3[], + groundMid: [ [-16, 0.1, 0], [14, 0.1, 0], ] as Point3[], + firstCrv: [ [-12, 0, -5], [-7, 0, -2], [0, 0, -4], [2, 0, -3], [12, 0, -3], ] as Point3[], + secondCrv: [ [-14, 2, -8], [-7, 1.3, -3], [0, 1.8, -5.8], [2, 2, -5], [14, 1.5, -4], ] as Point3[], + midCrv: [ [-18, 4, 0], [-7, 5, 0], [0, 3.7, 0], [2, 3.7, 0], [12, 8, 0], ] as Point3[], + }; + + // Destructure OCCT modules for easier access + const { shapes, transforms, operations } = bitbybit.occt; + const { face } = shapes; // Specifically the face module + + // 2. Create Base Curves using Interpolation + const intOptions = new Inputs.OCCT.InterpolationDto(); // Options for interpolation + + intOptions.points = sd.groundCrv; + const groundCrv = await shapes.wire.interpolatePoints(intOptions); + shapesToClean.push(groundCrv); // Add to cleanup list + + // Mirror one of the ground curves to create symmetry + const mirrorOptions = new Inputs.OCCT.MirrorAlongNormalDto(); + mirrorOptions.normal = [0, 0, 1]; // Mirror across the XY plane (normal is Z-axis) + mirrorOptions.shape = groundCrv; + const groundCrvMir = await transforms.mirrorAlongNormal(mirrorOptions); + shapesToClean.push(groundCrvMir); + + // Create other guide curves similarly + intOptions.points = sd.groundMid; + const groundMid = await shapes.wire.interpolatePoints(intOptions); + shapesToClean.push(groundMid); + + intOptions.points = sd.firstCrv; + const firstCrv = await shapes.wire.interpolatePoints(intOptions); + mirrorOptions.shape = firstCrv; + const firstCrvMir = await transforms.mirrorAlongNormal(mirrorOptions); + shapesToClean.push(firstCrv, firstCrvMir); + + intOptions.points = sd.secondCrv; + const secondCrv = await shapes.wire.interpolatePoints(intOptions); + mirrorOptions.shape = secondCrv; + const secondCrvMir = await transforms.mirrorAlongNormal(mirrorOptions); + shapesToClean.push(secondCrv, secondCrvMir); + + intOptions.points = sd.midCrv; + const midCrv = await shapes.wire.interpolatePoints(intOptions); + shapesToClean.push(midCrv); + + // 3. Create the Main Lofted Surface (Shell of the House) + const loftOptions = new Inputs.OCCT.LoftAdvancedDto(); + loftOptions.shapes = [ + midCrv, secondCrv, firstCrv, groundCrv, groundMid, + groundCrvMir, firstCrvMir, secondCrvMir, midCrv, + ]; + loftOptions.straight = true; + const loft = await operations.loftAdvanced(loftOptions); + shapesToClean.push(loft); + + // 4. Extract Specific Faces from the Lofted Surface + const faceRoof = await face.getFace({ shape: loft, index: 0 }); + const faceWall = await face.getFace({ shape: loft, index: 1 }); + shapesToClean.push(faceRoof, faceWall); + + // 5. Generate Hexagonal Patterns on Roof and Walls + const roofHexCompounds = await createHexagonsRoof(faceRoof, model.uHex, model.vHex, bitbybit); + shapesToClean.push(...roofHexCompounds); + + const wallHexShape = await createHexagonsWalls(faceWall, model.uHex, Math.ceil(model.vHex / 2), bitbybit); + shapesToClean.push(wallHexShape); + + const wallExtrude = await operations.extrude({ shape: wallHexShape, direction: [0, 0, -0.2] }); + shapesToClean.push(wallExtrude); + + // 6. Mirror Roof and Wall Components + const mirroredRoofPromises = roofHexCompounds.map((r) => { + mirrorOptions.shape = r; + return transforms.mirrorAlongNormal(mirrorOptions); + }); + const mirroredRoofCompounds = await Promise.all(mirroredRoofPromises); + shapesToClean.push(...mirroredRoofCompounds); + + mirrorOptions.shape = wallExtrude; + const mirroredWall = await transforms.mirrorAlongNormal(mirrorOptions); + shapesToClean.push(mirroredWall); + + // 7. Combine All OCCT Parts + const allPartsForFinalCompound = [ + ...roofHexCompounds, ...mirroredRoofCompounds, + wallExtrude, mirroredWall, + ]; + const finalOcctShape = await shapes.compound.makeCompound({ shapes: allPartsForFinalCompound }); + shapesToClean.push(finalOcctShape); + + // Create sub-compounds for different materials/grouping + const compRoof1 = await shapes.compound.makeCompound({ shapes: [roofHexCompounds[0], mirroredRoofCompounds[0], wallExtrude, mirroredWall] }); + const compRoof2 = await shapes.compound.makeCompound({ shapes: [roofHexCompounds[1], mirroredRoofCompounds[1]] }); + const compRoof3 = await shapes.compound.makeCompound({ shapes: [roofHexCompounds[2], mirroredRoofCompounds[2]] }); + shapesToClean.push(compRoof1, compRoof2, compRoof3); + + // 8. Drawing the OCCT Shapes into PlayCanvas Application + const drawOptions = new Inputs.Draw.DrawOcctShapeOptions(); + drawOptions.precision = 0.19; + drawOptions.drawEdges = model.drawEdges; + drawOptions.drawFaces = model.drawFaces; + drawOptions.edgeColour = '#000000'; + + // Create PlayCanvas materials + const color = new pc.Color().fromString(model.color); + const mat1 = new pc.StandardMaterial(); + mat1.diffuse.copy(color); + mat1.update(); + + drawOptions.faceMaterial = mat1; + const entity1 = await bitbybit.draw.drawAnyAsync({ entity: compRoof1, options: drawOptions }); + + const mat2 = new pc.StandardMaterial(); + mat2.diffuse.set(0, 0, 1); + mat2.update(); + drawOptions.faceMaterial = mat2; + const entity2 = await bitbybit.draw.drawAnyAsync({ entity: compRoof2, options: drawOptions }); + + const mat3 = new pc.StandardMaterial(); + mat3.diffuse.set(0.2, 0, 1); + mat3.update(); + drawOptions.faceMaterial = mat3; + const entity3 = await bitbybit.draw.drawAnyAsync({ entity: compRoof3, options: drawOptions }); + + // Store references to the PlayCanvas entities + current.entities = [entity1, entity2, entity3]; + + // Enable shadows on all render components + current.entities.forEach((entity) => { + entity.findComponents('render').forEach(renderComp => { + renderComp.castShadows = true; + renderComp.receiveShadows = true; + }); + }); + + return finalOcctShape; + } + return undefined; +}; + +// Helper function to create hexagonal patterns for the walls +async function createHexagonsWalls( + faceToSubdivide: Inputs.OCCT.TopoDSFacePointer, + nrHexagonsU: number, + nrHexagonsV: number, + bitbybit: BitByBitBase +): Promise { + const { shapes } = bitbybit.occt; + const { face } = shapes; + + const hexSubdivisionOptions = new Inputs.OCCT.FaceSubdivideToHexagonHolesDto(); + hexSubdivisionOptions.shape = faceToSubdivide; + hexSubdivisionOptions.nrHexagonsU = nrHexagonsU; + hexSubdivisionOptions.nrHexagonsV = nrHexagonsV; + hexSubdivisionOptions.scalePatternU = [0.8, 0.5, 0.5, 0.3]; + hexSubdivisionOptions.scalePatternV = [0.8, 0.5, 0.5, 0.3]; + hexSubdivisionOptions.offsetFromBorderV = 0.1; + hexSubdivisionOptions.flatU = false; + hexSubdivisionOptions.inclusionPattern = [true, true, true, false]; + + const subdividedFaces = await face.subdivideToHexagonHoles(hexSubdivisionOptions); + return subdividedFaces[0]; +} + +// Helper function to create hexagonal panels for the roof +async function createHexagonsRoof( + faceToSubdivide: Inputs.OCCT.TopoDSFacePointer, + nrHexagonsU: number, + nrHexagonsV: number, + bitbybit: BitByBitBase +): Promise { + const { shapes, operations } = bitbybit.occt; + const { face, wire } = shapes; + + // Create outer hexagonal wires + const hexWiresOptionsOuter = new Inputs.OCCT.FaceSubdivideToHexagonWiresDto(); + hexWiresOptionsOuter.shape = faceToSubdivide; + hexWiresOptionsOuter.nrHexagonsU = nrHexagonsU; + hexWiresOptionsOuter.nrHexagonsV = nrHexagonsV; + hexWiresOptionsOuter.scalePatternU = [0.8, 0.5, 0.1, 0.1, 0.1]; + hexWiresOptionsOuter.scalePatternV = [0.8, 0.5, 0.1, 0.1, 0.1]; + hexWiresOptionsOuter.flatU = false; + hexWiresOptionsOuter.inclusionPattern = [true, true, true, false]; + const outerHexWires = await face.subdivideToHexagonWires(hexWiresOptionsOuter); + + // Create inner hexagonal wires + const hexWiresOptionsInner = new Inputs.OCCT.FaceSubdivideToHexagonWiresDto(); + hexWiresOptionsInner.shape = faceToSubdivide; + hexWiresOptionsInner.flatU = false; + hexWiresOptionsInner.nrHexagonsU = nrHexagonsU; + hexWiresOptionsInner.nrHexagonsV = nrHexagonsV; + hexWiresOptionsInner.inclusionPattern = [true, true, true, false]; + + const innerHexWiresRaw = await face.subdivideToHexagonWires(hexWiresOptionsInner); + const innerHexWiresReversed = await Promise.all(outerHexWires.map((s) => wire.reversedWire({ shape: s }))); + + // Create faces between pairs of wires + const panelFacePromises = innerHexWiresReversed.map((reversedInnerWire, index) => { + const outerWire = innerHexWiresRaw[index]; + return face.createFaceFromWires({ shapes: [outerWire, reversedInnerWire], planar: false }); + }); + const panelFaces = await Promise.all(panelFacePromises); + + // Apply thickness based on height pattern + const heightPattern = [0.11, 0.1, 0.1]; + let heightPatternIndex = 0; + + const groupPromises: [ + Promise[], + Promise[], + Promise[] + ] = [[], [], []]; + + panelFaces.forEach((panelFace) => { + const currentHeight = heightPattern[heightPatternIndex]; + heightPatternIndex = (heightPatternIndex + 1) % heightPattern.length; + + const thickSolidPromise = operations.makeThickSolidSimple({ + shape: panelFace, + offset: currentHeight, + }); + + if (heightPatternIndex === 0) groupPromises[0].push(thickSolidPromise); + else if (heightPatternIndex === 1) groupPromises[1].push(thickSolidPromise); + else groupPromises[2].push(thickSolidPromise); + }); + + const [thickSolidsGroup1, thickSolidsGroup2, thickSolidsGroup3] = await Promise.all([ + Promise.all(groupPromises[0]), + Promise.all(groupPromises[1]), + Promise.all(groupPromises[2]), + ]); + + const compound1 = await shapes.compound.makeCompound({ shapes: thickSolidsGroup1 }); + const compound2 = await shapes.compound.makeCompound({ shapes: thickSolidsGroup2 }); + const compound3 = await shapes.compound.makeCompound({ shapes: thickSolidsGroup3 }); + + return [compound1, compound2, compound3]; +}`} + + +**Dissecting `create-shape.ts`:** + +* **OCCT Memory Management:** Starts by calling `bitbybit.occt.deleteShapes()` to clear geometry from previous updates. +* **Defining Base Curves:** Arrays of 3D points act as control points for generating smooth NURBS curves. +* **Creating Guide Wires:** Uses `shapes.wire.interpolatePoints()` to create OCCT wire shapes from point arrays, with mirroring for symmetry. +* **Lofting the Main Shell:** Creates the Hex House form by lofting a surface through guide wires using `operations.loftAdvanced()`. +* **Extracting Faces:** Extracts specific faces for the roof and walls using `face.getFace()`. +* **Generating Hexagonal Patterns:** + * **`createHexagonsWalls()`:** Subdivides wall face into hexagonal holes using `face.subdivideToHexagonHoles()`. + * **`createHexagonsRoof()`:** Creates hexagonal panels with varying heights using `face.subdivideToHexagonWires()` and `operations.makeThickSolidSimple()`. +* **Mirroring and Compounding:** Mirrors components and combines them into compounds. +* **Drawing to PlayCanvas:** + * Creates PlayCanvas `StandardMaterial` instances with diffuse colors. + * Uses `bitbybit.draw.drawAnyAsync()` to convert OCCT geometry into PlayCanvas entities. + * Stores entity references in `current.entities` for rotation and disposal. + * Enables shadow casting and receiving on render components. + +## 4. Styles (`style.css`) + +The CSS provides basic page styling, positions the logo, and includes styles for the loading spinner animation. + +## Conclusion + +This Hex House tutorial demonstrates how to combine the parametric power of Bitbybit's OCCT integration with the rendering capabilities of PlayCanvas to create complex and interactive 3D architectural concepts. Key takeaways include: + +* **Modular Design:** Structuring the application into `main.ts` for orchestration and helper files for specific tasks. +* **Parametric CAD with OCCT:** Utilizing advanced OCCT operations through Bitbybit's API. +* **OCCT Memory Management:** Tracking intermediate OCCT shapes and using `bitbybit.occt.deleteShapes()` to prevent memory leaks. +* **Dynamic Updates:** Efficiently updating the PlayCanvas app by destroying old entities and redrawing new ones based on GUI-driven parameter changes. +* **PlayCanvas Integration:** Using `bitbybit.draw.drawAnyAsync()` to convert OCCT geometry into PlayCanvas entities with materials. +* **Entity-Component System:** PlayCanvas uses entities with components, and Bitbybit creates and manages these entities for rendering. + +This example serves as a blueprint for developing sophisticated web-based 3D applications where detailed geometric control and user interactivity are paramount. diff --git a/docs/learn/npm-packages/playcanvas/intro.md b/docs/learn/npm-packages/playcanvas/intro.md new file mode 100644 index 00000000..c2cbc2e7 --- /dev/null +++ b/docs/learn/npm-packages/playcanvas/intro.md @@ -0,0 +1,93 @@ +--- +sidebar_position: 1 +title: "Introduction to PlayCanvas" +sidebar_label: "PlayCanvas Overview" +description: "An overview of PlayCanvas, a powerful WebGL game engine for creating and displaying 3D graphics in web browsers, and how it's integrated with Bitbybit." +tags: [npm-packages, playcanvas] +--- + +import Admonition from '@theme/Admonition'; + +# Introduction to PlayCanvas: A Powerful WebGL Game Engine + +PlayCanvas is a visual development platform for interactive 3D content. It's a complete game engine built on WebGL, providing developers with tools to create high-performance 3D applications that run directly in web browsers. If you're looking to build games, product configurators, or interactive 3D experiences with professional tooling, PlayCanvas is an excellent choice. + +**Visit the official PlayCanvas homepage:** playcanvas.com + +PlayCanvas Logo + + +## What is PlayCanvas Capable Of? + +PlayCanvas is a comprehensive game engine and development platform that combines a powerful runtime engine with an intuitive visual editor. It provides everything needed to create professional 3D content for the web. + +Key capabilities and features include: + +* **Visual Editor:** A cloud-based, collaborative editor that runs in your browser, allowing teams to work together in real-time on 3D projects. +* **Entity-Component System:** An efficient architecture for organizing game objects and their behaviors, making it easy to build complex interactive scenes. +* **Advanced Rendering:** High-performance WebGL-based rendering with support for: + * Physically-based rendering (PBR) materials + * Real-time shadows and lighting + * HDR rendering and post-processing effects + * Particle systems + * Skeletal animation +* **Asset Pipeline:** Comprehensive support for importing and optimizing 3D models, textures, audio, and other assets from industry-standard tools like Blender, Maya, and 3ds Max. +* **Physics Engine:** Integrated physics simulation using Ammo.js (a port of Bullet Physics), enabling realistic collisions, rigid body dynamics, and constraints. +* **Audio:** Full 3D positional audio system with support for multiple audio formats. +* **Input Handling:** Cross-platform input support for keyboard, mouse, touch, and gamepad controls. +* **Scripting:** JavaScript and TypeScript support for game logic and interactions. +* **Networking:** Built-in support for multiplayer experiences and real-time communication. +* **Optimization Tools:** Profiling tools, asset compression, and performance monitoring to ensure smooth experiences across devices. +* **Publishing:** One-click deployment to web hosting with automatic optimization and CDN distribution. + +## Why Choose PlayCanvas? + +PlayCanvas has gained recognition in the industry for several compelling reasons: + +* **Performance:** One of the fastest WebGL engines available, capable of delivering console-quality graphics in the browser. +* **Cloud-Based Workflow:** The online editor enables teams to collaborate without complex setup, with version control built-in. +* **Instant Publishing:** Deploy your projects instantly to the web with no build process required. +* **Mobile-First:** Optimized for mobile devices, ensuring your 3D content runs smoothly on smartphones and tablets. +* **Open Source Engine:** The PlayCanvas engine itself is open source (MIT licensed), giving you full control and transparency. +* **Professional Support:** Enterprise-grade support and hosting options for commercial projects. +* **Rich Ecosystem:** Active community, extensive documentation, and numerous examples and tutorials. +* **Cross-Platform:** Write once, run everywhere - from high-end desktops to mobile devices. + +## Learning Resources & Community + +If you're interested in diving deeper into PlayCanvas, here are some valuable resources: + +* **Official Documentation:** PlayCanvas Developer Documentation - Comprehensive guides and API references. +* **Official Tutorials:** PlayCanvas Tutorials - Step-by-step tutorials for getting started. +* **PlayCanvas Examples:** PlayCanvas Examples - Live examples showcasing various features. +* **PlayCanvas Forum:** forum.playcanvas.com - The official community forum for discussions and support. +* **GitHub Repository:** playcanvas/engine - Explore the open-source engine code. +* **YouTube Channel:** PlayCanvas YouTube - Video tutorials and showcases. +* **Discord Community:** PlayCanvas Discord - Real-time chat with the community. + +## How Bitbybit Uses PlayCanvas + +Bitbybit's core architecture is designed to be game engine agnostic, meaning its fundamental geometry processing and CAD algorithms are independent of any specific rendering engine. However, to display 3D content in a web browser, a rendering engine is essential. + +PlayCanvas is one of the rendering engines for which Bitbybit provides an official integration layer. + +* **Integration Package:** We offer the `@bitbybit-dev/playcanvas` NPM package. This package acts as a bridge, taking the geometric data generated by Bitbybit's core (which might originate from OCCT, JSCAD, or other kernels) and translating it into PlayCanvas specific objects like `pc.Entity`, `pc.GraphNode`, and meshes with appropriate rendering components. +* **Drawing Logic:** Functions within this package, notably `drawAnyAsync`, are responsible for efficiently creating and rendering these PlayCanvas objects within your PlayCanvas application. + + + PlayCanvas support is only offered via our npm package. Due to historical reasons for our visual programming editors we use BabylonJS game engine - another great open-source WebGL engine. + + +By using `@bitbybit-dev/playcanvas`, developers can leverage Bitbybit's powerful CAD and computational geometry features while working within the performant and feature-rich PlayCanvas ecosystem. + +**Source Code for Bitbybit's PlayCanvas Integration:** +You can find the source code for our PlayCanvas integration package in our main GitHub repository, typically within a path like: +bitbybit-dev/bitbybit/tree/master/packages/dev/playcanvas + +--- + +Whether you're building interactive games, immersive product configurators, architectural visualizations, or innovative 3D web experiences, understanding PlayCanvas can significantly enhance your capabilities as a web developer. Bitbybit's integration aims to make it easier to combine powerful geometry generation with this high-performance rendering engine. diff --git a/docs/learn/npm-packages/playcanvas/start-with-playcanvas.md b/docs/learn/npm-packages/playcanvas/start-with-playcanvas.md new file mode 100644 index 00000000..4e3efa4f --- /dev/null +++ b/docs/learn/npm-packages/playcanvas/start-with-playcanvas.md @@ -0,0 +1,619 @@ +--- +sidebar_position: 2 +title: Using Bitbybit with PlayCanvas +sidebar_label: PlayCanvas Starter Template +description: Learn how to set up and use the @bitbybit-dev/playcanvas package with Vite to create 3D CAD applications, and control which geometry kernels (OCCT, JSCAD, Manifold) are initialized. +tags: [npm-packages, playcanvas, occt, manifold, jscad] +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; +import Admonition from '@theme/Admonition'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import CodeBlock from '@theme/CodeBlock'; + +# Using Bitbybit with PlayCanvas + +This guide will walk you through setting up and using the `@bitbybit-dev/playcanvas` package to integrate Bitbybit's 3D CAD functionalities into your PlayCanvas applications. We'll use Vite as our build tool, which simplifies the setup process. + +The `@bitbybit-dev/playcanvas` package conveniently includes `playcanvas` as a dependency, so you don't need to install it separately. + +## Prerequisites + +* Node.js and npm (or yarn) installed. +* A basic understanding of TypeScript and PlayCanvas. + +## [Example on Bitbybit Github Repo](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/vite/playcanvas/starter-template) + +## 1. Project Setup with Vite + +First, create a new Vite project with a TypeScript template: + + +{`npm create vite@latest my-bitbybit-playcanvas-app -- --template vanilla-ts +# or: yarn create vite my-bitbybit-playcanvas-app --template vanilla-ts + +cd my-bitbybit-playcanvas-app`} + + +Next, install the Bitbybit PlayCanvas package and its necessary worker dependencies: + + +{`npm install @bitbybit-dev/playcanvas +# or: yarn add @bitbybit-dev/playcanvas`} + + + +
    +
  • @bitbybit-dev/playcanvas: The main library for integrating Bitbybit with PlayCanvas. It also installs these main packages listed below.
  • +
  • playcanvas: Provides main game engine to be used with this demo.
  • +
  • @bitbybit-dev/core: Collects all kernel web worker libraries into coherent bitbybit base. It also includes some higher level functionality.
  • +
  • @bitbybit-dev/occt-worker: Provides the OpenCascade (OCCT) geometry kernel running in a Web Worker.
  • +
  • @bitbybit-dev/jscad-worker: Provides the JSCAD geometry kernel running in a Web Worker.
  • +
  • @bitbybit-dev/manifold-worker: Provides the Manifold geometry kernel running in a Web Worker.
  • +
  • @bitbybit-dev/occt: Communicates with worker and contains main logic of OCCT geometry kernel - can be used in non web-worker environments.
  • +
  • @bitbybit-dev/jscad: Communicates with worker and contains main logic of JSCAD geometry kernel - can be used in non web-worker environments.
  • +
  • @bitbybit-dev/manifold: Communicates with worker and contains main logic of Manifold geometry kernel - can be used in non web-worker environments.
  • +
  • @bitbybit-dev/base: Contains base geometry types and functions, such as vector operations, matrix transformations, math and list helpers - they can be used in all kernels.
  • +
+
+ +## 2. HTML Structure + +Modify your `index.html` file in the project root to include a `` element where the PlayCanvas application will be rendered: + + +{` + + + + + + Bitbybit & PlayCanvas Example + + + + + +`} + + +This is a standard HTML setup. The key parts are: +- ``: This is where our 3D scene will be drawn. +- ``: This loads our main TypeScript application logic. + +## 3. Setting up Web Workers + +Bitbybit utilizes Web Workers to run computationally intensive geometry kernels (OCCT, JSCAD, Manifold) off the main browser thread, preventing UI freezes. You need to create simple worker files that initialize these kernels. + +Create a `workers` directory inside your `src` folder (`src/workers/`). + + + + + +{`import initOpenCascade from '@bitbybit-dev/occt/bitbybit-dev-occt/cdn'; +import type { OpenCascadeInstance } from '@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.js'; +import { + initializationComplete, + onMessageInput, +} from '@bitbybit-dev/occt-worker'; + +// Initialize OpenCascade (OCCT) +initOpenCascade().then((occ: OpenCascadeInstance) => { + // Notify the main thread that OCCT is ready + initializationComplete(occ, undefined); +}); + +// Listen for messages from the main thread +addEventListener('message', ({ data }) => { + // Process messages using the occt-worker helper + onMessageInput(data, postMessage); +});`} + +**Explanation:** +- Imports `initOpenCascade` to load the OCCT WebAssembly module. +- Calls `initializationComplete` once OCCT is loaded, signaling to the main Bitbybit instance that this kernel is ready. +- `onMessageInput` handles communication between the main thread and the OCCT worker. + + + + + +{`import { + initializationComplete, + onMessageInput, +} from '@bitbybit-dev/jscad-worker'; + +// Dynamically import and initialize JSCAD +import('@bitbybit-dev/jscad/jscad-generated').then((s) => { + // Notify the main thread that JSCAD is ready + initializationComplete(s.default()); +}); + +// Listen for messages from the main thread +addEventListener('message', ({ data }) => { + // Process messages using the jscad-worker helper + onMessageInput(data, postMessage); +});`} + +**Explanation:** +- Dynamically imports the JSCAD module. +- Calls `initializationComplete` once JSCAD is loaded. +- `onMessageInput` handles communication. + + + + + +{`import { + initializationComplete, + onMessageInput, +} from '@bitbybit-dev/manifold-worker'; +import Module from 'manifold-3d'; // Imports the Manifold JS bindings + +const init = async () => { + // Initialize the Manifold WASM module + const wasm = await Module({ + // Manifold requires its WASM file to be loaded. + // This CDN link provides a hosted version. + // For production, you might want to host this yourself. + locateFile: () => { + return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm'; + }, + }); + wasm.setup(); // Additional setup step for Manifold + // Notify the main thread that Manifold is ready + initializationComplete(wasm); +}; + +init(); + +// Listen for messages from the main thread +addEventListener('message', ({ data }) => { + // Process messages using the manifold-worker helper + onMessageInput(data, postMessage); +});`} + +**Explanation:** +- Imports the `manifold-3d` JavaScript bindings. +- The `Module` function initializes the Manifold WASM. The `locateFile` function is crucial for pointing to the Manifold WASM file. +- Calls `initializationComplete` after setup. +- `onMessageInput` handles communication. + + + + + + Vite handles these worker files automatically when you instantiate them using `new Worker(new URL('./path/to/worker.ts', import.meta.url), ...)`. Ensure the paths in `main.ts` correctly point to these files within your `src/workers/` directory. + + +## 4. Main Application Logic (`main.ts`) + +Replace the content of `src/main.ts` with the following: + + +{`import './style.css'; // Basic styling +import { BitByBitBase, Inputs } from '@bitbybit-dev/playcanvas'; +import { OccStateEnum } from '@bitbybit-dev/occt-worker'; +import { JscadStateEnum } from '@bitbybit-dev/jscad-worker'; +import { ManifoldStateEnum } from '@bitbybit-dev/manifold-worker'; + +import { first, firstValueFrom, map } from 'rxjs'; +import { + Application, + Color, + Entity, + FILLMODE_FILL_WINDOW, + Mouse, + RESOLUTION_AUTO, + TouchDevice, +} from 'playcanvas'; + +// Define an interface for kernel options +interface KernelOptions { + enableOCCT: boolean; + enableJSCAD: boolean; + enableManifold: boolean; +} + +// --- 1. Main Application Entry Point --- +start(); + +async function start() { + // Initialize basic PlayCanvas scene + const { app, scene, camera } = initPlayCanvas(); + + // Create an instance of BitByBitBase for PlayCanvas + const bitbybit = new BitByBitBase(); + + // --- 2. Configure and Initialize Kernels --- + // Users can control which kernels are loaded + const kernelOptions: KernelOptions = { + enableOCCT: true, + enableJSCAD: true, + enableManifold: true, + }; + // Initialize Bitbybit with the selected kernels + await initWithKernels(app, scene, bitbybit, kernelOptions); + + // Setup orbit camera controls using bitbybit library + const cameraOptions = new Inputs.PlayCanvasCamera.OrbitCameraDto(); + cameraOptions.distance = 125; + cameraOptions.pitch = -24; + cameraOptions.yaw = 27; + cameraOptions.frameOnStart = false; + cameraOptions.inertiaFactor = 0.2; + cameraOptions.distanceSensitivity = 0.3; + cameraOptions.focusEntity = camera; + bitbybit.playcanvas.camera.orbitCamera.create(cameraOptions); + + // --- 3. Create Geometry with Active Kernels --- + if (kernelOptions.enableOCCT) { + await createOCCTGeometry(bitbybit, '#ff0000'); // Red + } + if (kernelOptions.enableManifold) { + await createManifoldGeometry(bitbybit, '#00ff00'); // Green + } + if (kernelOptions.enableJSCAD) { + await createJSCADGeometry(bitbybit, '#0000ff'); // Blue + } +} + +// --- 4. PlayCanvas Scene Initialization --- +function initPlayCanvas() { + const canvas = document.getElementById('playcanvas-canvas') as HTMLCanvasElement; + + // Create a PlayCanvas application + const app = new Application(canvas, { + graphicsDeviceOptions: { + antialias: true, + alpha: false, + }, + mouse: new Mouse(canvas), + touch: new TouchDevice(canvas), + }); + + // Fill the window and automatically change resolution to be the same as the canvas size + app.setCanvasFillMode(FILLMODE_FILL_WINDOW); + app.setCanvasResolution(RESOLUTION_AUTO); + + // Ensure canvas is resized when window changes size + window.addEventListener('resize', () => app.resizeCanvas()); + + // Create root scene entity + const scene = new Entity('scene'); + app.root.addChild(scene); + + // Create camera entity + const camera = new Entity('camera'); + camera.addComponent('camera', { + clearColor: new Color(0x1a / 255, 0x1c / 255, 0x1f / 255, 1), + fov: 70, + nearClip: 0.1, + farClip: 1000, + }); + scene.addChild(camera); + + // Create directional light + const light = new Entity('directionalLight'); + light.addComponent('light', { + type: 'directional', + color: new Color(1, 1, 1), + intensity: 1, + }); + light.setEulerAngles(45, 30, 0); + scene.addChild(light); + + // Create ambient/hemisphere-like lighting + app.scene.ambientLight = new Color(0.4, 0.4, 0.4); + + // Start the application update loop + app.start(); + + return { app, scene, camera }; +} + +// --- 5. Bitbybit Kernel Initialization Logic --- +async function initWithKernels( + app: pc.AppBase, + scene: pc.Entity, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string; initializedKernels: string[] }> { + let occtWorkerInstance: Worker | undefined; + let jscadWorkerInstance: Worker | undefined; + let manifoldWorkerInstance: Worker | undefined; + + // 1. Conditionally create worker instances + if (options.enableOCCT) { + occtWorkerInstance = new Worker( + new URL('./workers/occt.worker.ts', import.meta.url), + { name: 'OCC_WORKER', type: 'module' } + ); + } + if (options.enableJSCAD) { + jscadWorkerInstance = new Worker( + new URL('./workers/jscad.worker.ts', import.meta.url), + { name: 'JSCAD_WORKER', type: 'module' } + ); + } + if (options.enableManifold) { + manifoldWorkerInstance = new Worker( + new URL('./workers/manifold.worker.ts', import.meta.url), + { name: 'MANIFOLD_WORKER', type: 'module' } + ); + } + + // 2. Initialize Bitbybit with PlayCanvas app and scene + bitbybit.init( + app, + scene, + occtWorkerInstance, + jscadWorkerInstance, + manifoldWorkerInstance + ); + + // 3. Collect promises for kernel initializations + const initializationPromises: Promise[] = []; + let anyKernelSelectedForInit = false; + + if (options.enableOCCT) { + anyKernelSelectedForInit = true; + if (bitbybit.occtWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => 'OCCT') + ) + ) + ); + } else { + console.warn( + 'OCCT enabled in options, but occtWorkerManager not found after init.' + ); + } + } + + if (options.enableJSCAD) { + anyKernelSelectedForInit = true; + if (bitbybit.jscadWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => 'JSCAD') + ) + ) + ); + } else { + console.warn( + 'JSCAD enabled in options, but jscadWorkerManager not found after init.' + ); + } + } + + if (options.enableManifold) { + anyKernelSelectedForInit = true; + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => 'Manifold') + ) + ) + ); + } else { + console.warn( + 'Manifold enabled in options, but manifoldWorkerManager not found after init.' + ); + } + } + + // 4. Wait for selected & available kernels or handle no selection/availability + if (!anyKernelSelectedForInit) { + console.log('No kernels selected for initialization.'); + return { message: 'No kernels selected for initialization.', initializedKernels: [] }; + } + + if (initializationPromises.length === 0) { + console.log( + 'Kernels were selected, but none had managers available for awaiting initialization.' + ); + return { + message: 'Selected kernels were not awaitable for initialization state.', + initializedKernels: [], + }; + } + + const initializedKernels = await Promise.all(initializationPromises); + console.log('Kernels initialized:', initializedKernels.join(', ')); + console.log('Kernels initialized:', initializedKernels.join(', ')); + return { + message: \`Successfully initialized: \${initializedKernels.join(', ')}\`, + initializedKernels, + }; +} + +// --- 6. Geometry Creation Functions (Examples) --- +async function createOCCTGeometry(bitbybit: BitByBitBase, color: string) { + console.log('Creating OCCT geometry...'); + const cubeOptions = new Inputs.OCCT.CubeDto(); + cubeOptions.size = 25; + cubeOptions.center = [0, 0, 0]; + + const cube = await bitbybit.occt.shapes.solid.createCube(cubeOptions); + + const filletOptions = new Inputs.OCCT.FilletDto(); + filletOptions.shape = cube; + filletOptions.radius = 4; + const roundedCube = await bitbybit.occt.fillets.filletEdges(filletOptions); + + const drawOptions = new Inputs.Draw.DrawOcctShapeOptions(); + drawOptions.edgeWidth = 5; + drawOptions.faceColour = color; + drawOptions.drawVertices = true; + drawOptions.vertexSize = 0.5; + drawOptions.vertexColour = '#ffffff'; + await bitbybit.draw.drawAnyAsync({ + entity: roundedCube, + options: drawOptions, + }); + console.log('OCCT geometry created and drawn.'); +} + +async function createManifoldGeometry(bitbybit: BitByBitBase, color: string) { + console.log('Creating Manifold geometry...'); + const sphereOptions = new Inputs.Manifold.SphereDto(); + sphereOptions.radius = 15; + const sphere = await bitbybit.manifold.manifold.shapes.sphere(sphereOptions); + + const cubeOptions = new Inputs.Manifold.CubeDto(); + cubeOptions.size = 25; + const cube = await bitbybit.manifold.manifold.shapes.cube(cubeOptions); + + const diffedShape = await bitbybit.manifold.manifold.booleans.differenceTwo({ + manifold1: cube, + manifold2: sphere, + }); + + const translationOptions = new Inputs.Manifold.TranslateDto(); + translationOptions.manifold = diffedShape; + translationOptions.vector = [0, -40, 0]; // Position below OCCT + const movedShape = await bitbybit.manifold.manifold.transforms.translate( + translationOptions + ); + + const drawOptions = new Inputs.Draw.DrawManifoldOrCrossSectionOptions(); + drawOptions.faceColour = color; + await bitbybit.draw.drawAnyAsync({ + entity: movedShape, + options: drawOptions, + }); + console.log('Manifold geometry created and drawn.'); +} + +async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { + console.log('Creating JSCAD geometry...'); + const geodesicSphereOptions = new Inputs.JSCAD.GeodesicSphereDto(); + geodesicSphereOptions.radius = 15; + geodesicSphereOptions.center = [0, 40, 0]; // Position above OCCT + const geodesicSphere = await bitbybit.jscad.shapes.geodesicSphere( + geodesicSphereOptions + ); + + // Example: Create another simple sphere for a boolean operation + const sphereOptions = new Inputs.JSCAD.SphereDto(); + sphereOptions.radius = 10; // Smaller sphere + sphereOptions.center = [5, 45, 0]; // Slightly offset + const simpleSphere = await bitbybit.jscad.shapes.sphere(sphereOptions); + + const unionOptions = new Inputs.JSCAD.BooleanTwoObjectsDto(); + unionOptions.first = geodesicSphere; + unionOptions.second = simpleSphere; + const unionShape = await bitbybit.jscad.booleans.unionTwo(unionOptions); + + const drawOptions = new Inputs.Draw.DrawBasicGeometryOptions(); + drawOptions.colours = color; // Note: 'colours' for JSCAD draw options + await bitbybit.draw.drawAnyAsync({ + entity: unionShape, + options: drawOptions, + }); + console.log('JSCAD geometry created and drawn.'); +}`} + + +### Explanation of `main.ts`: + +1. **Imports:** + * `BitByBitBase` and `Inputs`: Core components from `@bitbybit-dev/playcanvas`. `Inputs` provides DTOs (Data Transfer Objects) for specifying parameters for geometry operations. + * `...StateEnum`: Enums used to check the initialization state of each kernel worker. + * PlayCanvas modules (`Application`, `Color`, `Entity`, etc.) for scene setup. + * `first`, `firstValueFrom`, `map` from `rxjs`: Used to subscribe to and transform the kernel state observables. + +2. **`KernelOptions` Interface:** Defines the structure for selecting which kernels to initialize. + +3. **`start()` function (Main Entry Point):** + * Calls `initPlayCanvas()` to set up the basic PlayCanvas application, scene entity, camera, and lights. + * Creates an instance of `BitByBitBase`. + * **`kernelOptions`**: This object is key. By setting `enableOCCT`, `enableJSCAD`, and `enableManifold` to `true` or `false`, you control which kernels Bitbybit attempts to initialize. This allows for optimizing load times and resource usage if not all kernels are needed. + * Calls `initWithKernels()` to initialize Bitbybit with the selected kernels. + * Sets up orbit camera controls using bitbybit's built-in camera helper. + * Conditionally calls geometry creation functions based on which kernels were enabled. + +4. **`initPlayCanvas()` function:** + * Creates a PlayCanvas `Application` instance with the canvas element. + * Sets up proper canvas fill mode and resolution handling. + * Creates a root `scene` entity and adds it to the app root. + * Creates a `camera` entity with configured clear color, FOV, and clipping planes. + * Adds directional and ambient lighting. + * Starts the application and returns the `app`, `scene`, and `camera`. + +5. **`initWithKernels()` function:** + * This is the core of Bitbybit's initialization. + * Conditionally creates `Worker` instances for OCCT, JSCAD, and Manifold based on the `options` passed in. + * Calls `bitbybit.init(app, scene, occtWorkerInstance, jscadWorkerInstance, manifoldWorkerInstance)`. Both the PlayCanvas `app` and `scene` entity are passed. + * Uses RxJS `pipe(first(...), map(...))` to subscribe to the state observables of each enabled kernel and transform the result to a string identifying the kernel. + * The `Promise` resolves only after all selected kernels have emitted an `initialised` state. + * Returns an object with a message and an array of initialized kernel names. + +6. **Geometry Creation Functions:** + * These demonstrate how to use the APIs for each kernel. + * **`Inputs` DTOs**: Typed objects to pass parameters to Bitbybit's geometry functions. + * **API Structure**: Operations are namespaced under `bitbybit.occt.*`, `bitbybit.manifold.*`, and `bitbybit.jscad.*`. + * **Drawing**: `bitbybit.draw.drawAnyAsync()` renders entities into the PlayCanvas scene. The OCCT draw options now support additional properties like `edgeWidth`, `drawVertices`, `vertexSize`, and `vertexColour`. + +## 5. Basic Styling (Optional) + +Create an `src/style.css` file: + + +{`body { + margin: 0; + overflow: hidden; /* Prevent scrollbars from canvas */ + background-color: #1a1c1f; +} + +#playcanvas-canvas { + display: block; + width: 100vw; + height: 100vh; +}`} + + +## 6. Running the Application + + +{`npm run dev +# or: yarn dev`} + + +Vite will start a development server, and you should see a browser window open with your PlayCanvas application. If all kernels were enabled, you'll see three distinct shapes: +* A red, filleted cube (OCCT) at the origin. +* A green, subtracted shape (Manifold) positioned below the OCCT shape. +* A blue, unioned sphere shape (JSCAD) positioned above the OCCT shape. + +Check your browser's developer console for logs indicating the initialization status of each kernel and any errors. + +## Live Demo (StackBlitz) + +You can explore and interact with a live example of this setup on StackBlitz: + + +## Key Takeaways + +* **Vite Simplifies Setup:** Vite handles worker bundling and module resolution effectively. +* **`KernelOptions` for Control:** You have fine-grained control over which geometry kernels are loaded, allowing you to tailor the application to specific needs and optimize performance. +* **Asynchronous Operations:** Most Bitbybit operations are `async` because they communicate with Web Workers. +* **`Inputs` DTOs:** Use these typed objects to configure geometry operations. +* **Separate Worker Files:** Each kernel runs in its own dedicated worker. +* **Modular Design:** The `@bitbybit-dev/playcanvas` package neatly integrates these complex components for use with PlayCanvas. +* **Entity-Based Rendering:** PlayCanvas uses an entity-component system, and Bitbybit creates entities that are added to the app root. + +This setup provides a robust foundation for building sophisticated 3D CAD applications in the browser with Bitbybit and PlayCanvas. diff --git a/docs/learn/npm-packages/threejs/start-with-three-js.md b/docs/learn/npm-packages/threejs/start-with-three-js.md index 23e1af05..e8ecc821 100644 --- a/docs/learn/npm-packages/threejs/start-with-three-js.md +++ b/docs/learn/npm-packages/threejs/start-with-three-js.md @@ -159,7 +159,7 @@ const init = async () => { // This CDN link provides a hosted version. // For production, you might want to host this yourself. locateFile: () => { - return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@latest/wasm/manifold-3-3-2.wasm'; + return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm'; }, }); wasm.setup(); // Additional setup step for Manifold @@ -202,14 +202,12 @@ import { ManifoldStateEnum } from '@bitbybit-dev/manifold-worker'; import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; import { Color, - Group, HemisphereLight, PerspectiveCamera, Scene, - Vector3, WebGLRenderer, } from 'three'; -import { first, firstValueFrom, tap } from 'rxjs'; +import { first, firstValueFrom, map } from 'rxjs'; // Define an interface for kernel options interface KernelOptions { @@ -268,10 +266,11 @@ function initThreeJS() { const renderer = new WebGLRenderer({ antialias: true, canvas: domNode }); renderer.setSize(window.innerWidth, window.innerHeight); - // renderer.setPixelRatio(window.devicePixelRatio); // Consider devicePixelRatio for sharpness + // Set pixel ratio for sharper rendering + renderer.setPixelRatio(window.devicePixelRatio); camera.position.set(50, 50, 100); // Adjusted camera position - camera.lookAt(0,0,0); + camera.lookAt(0, 0, 0); const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; @@ -300,7 +299,7 @@ async function initWithKernels( scene: Scene, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -308,20 +307,20 @@ async function initWithKernels( // 1. Conditionally create worker instances if (options.enableOCCT) { occtWorkerInstance = new Worker( - new URL('./workers/occt.worker.ts', import.meta.url), - { name: 'OCC_WORKER', type: 'module' } + new URL('./workers/occt.worker.ts', import.meta.url), + { name: 'OCC_WORKER', type: 'module' } ); } if (options.enableJSCAD) { jscadWorkerInstance = new Worker( - new URL('./workers/jscad.worker.ts', import.meta.url), - { name: 'JSCAD_WORKER', type: 'module' } + new URL('./workers/jscad.worker.ts', import.meta.url), + { name: 'JSCAD_WORKER', type: 'module' } ); } if (options.enableManifold) { manifoldWorkerInstance = new Worker( - new URL('./workers/manifold.worker.ts', import.meta.url), - { name: 'MANIFOLD_WORKER', type: 'module' } + new URL('./workers/manifold.worker.ts', import.meta.url), + { name: 'MANIFOLD_WORKER', type: 'module' } ); } @@ -334,83 +333,85 @@ async function initWithKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { anyKernelSelectedForInit = true; if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log('OCCT Initialized')) - ) - ).then(() => {}) // Ensure the promise resolves to void for Promise.all - ); + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => 'OCCT') + ) + ) + ); } else { - console.warn( - 'OCCT enabled in options, but occtWorkerManager not found after init.' - ); + console.warn( + 'OCCT enabled in options, but occtWorkerManager not found after init.' + ); } } if (options.enableJSCAD) { anyKernelSelectedForInit = true; if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log('JSCAD Initialized')) - ) - ).then(() => {}) - ); + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => 'JSCAD') + ) + ) + ); } else { - console.warn( - 'JSCAD enabled in options, but jscadWorkerManager not found after init.' - ); + console.warn( + 'JSCAD enabled in options, but jscadWorkerManager not found after init.' + ); } } if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log('Manifold Initialized')) - ) - ).then(() => {}) - ); + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => 'Manifold') + ) + ) + ); } else { - console.warn( - 'Manifold enabled in options, but manifoldWorkerManager not found after init.' - ); + console.warn( + 'Manifold enabled in options, but manifoldWorkerManager not found after init.' + ); } } // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log('No kernels selected for initialization.'); - return { message: 'No kernels selected for initialization.' }; + return { message: 'No kernels selected for initialization.', initializedKernels: [] }; } if (initializationPromises.length === 0) { // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) console.log( - 'Kernels were selected, but none had managers available for awaiting initialization.' + 'Kernels were selected, but none had managers available for awaiting initialization.' ); return { - message: 'Selected kernels were not awaitable for initialization state.', + message: 'Selected kernels were not awaitable for initialization state.', + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log('Selected and awaitable kernels initialized:', options); + const initializedKernels = await Promise.all(initializationPromises); + console.log('Kernels initialized:', initializedKernels.join(', ')); return { - message: 'Selected and awaitable kernels initialized successfully.', + message: \`Successfully initialized: \${initializedKernels.join(', ')}\`, + initializedKernels, }; } @@ -429,7 +430,20 @@ async function createOCCTGeometry(bitbybit: BitByBitBase, color: string) { const roundedCube = await bitbybit.occt.fillets.filletEdges(filletOptions); const drawOptions = new Inputs.Draw.DrawOcctShapeOptions(); + drawOptions.edgeWidth = 5; drawOptions.faceColour = color; + drawOptions.drawVertices = true; + // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) + console.log( + 'Kernels were selected, but none had managers available for awaiting initialization.' + ); + return { + message: 'Selected kernels were not awaitable for initialization state.', + }; + } + + drawOptions.vertexSize = 0.5; + drawOptions.vertexColour = '#ffffff'; await bitbybit.draw.drawAnyAsync({ entity: roundedCube, options: drawOptions, @@ -504,7 +518,7 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { * `BitByBitBase` and `Inputs`: Core components from `@bitbybit-dev/threejs`. `Inputs` provides DTOs (Data Transfer Objects) for specifying parameters for geometry operations. * `...StateEnum`: Enums used to check the initialization state of each kernel worker. * Standard ThreeJS modules for scene setup. - * `first` from `rxjs`: Used to easily subscribe to the first emission of a kernel's state. + * `first`, `firstValueFrom`, `map` from `rxjs`: Used to subscribe to and transform the kernel state observables. 2. **`KernelOptions` Interface:** Defines the structure for selecting which kernels to initialize. @@ -517,20 +531,22 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { 4. **`initThreeJS()` function:** * Standard ThreeJS boilerplate: sets up the `Scene`, `PerspectiveCamera`, `WebGLRenderer`, `HemisphereLight`, and `OrbitControls`. + * Sets pixel ratio for sharper rendering on high-DPI displays. * Includes a window resize listener and an animation loop. 5. **`initWithKernels()` function:** * This is the core of Bitbybit's initialization. * It conditionally creates `Worker` instances for OCCT, JSCAD, and Manifold based on the `options` passed in. The `new URL('./workers/worker-name.worker.ts', import.meta.url)` syntax is Vite's way of correctly bundling and referencing web worker files. * Calls `await bitbybit.init(scene, occtWorkerInstance, jscadWorkerInstance, manifoldWorkerInstance)`. `BitByBitBase` can handle `undefined` for worker instances it shouldn't initialize. - * It then uses RxJS `pipe(first(...))` to subscribe to the state observables of each enabled kernel (`occWorkerState$`, `jscadWorkerState$`, `manifoldWorkerState$`). + * Uses RxJS `pipe(first(...), map(...))` to subscribe to the state observables of each enabled kernel and transform the result to a string identifying the kernel. * The `Promise` resolves only after all *selected and enabled* kernels have emitted an `initialised` state. This ensures that you don't try to use a kernel before it's ready. + * Returns an object with a message and an array of initialized kernel names. 6. **Geometry Creation Functions (`createOCCTGeometry`, `createManifoldGeometry`, `createJSCADGeometry`):** * These are example functions demonstrating how to use the APIs for each kernel. * **`Inputs` DTOs**: You'll notice the use of `Inputs.OCCT.CubeDto()`, `Inputs.Manifold.SphereDto()`, etc. These objects are used to pass parameters to Bitbybit's geometry creation and modification functions. They provide type safety and often mirror the inputs you'd find in a visual programming environment. Intellisense (auto-completion in your IDE) will be very helpful here. * **API Structure**: Operations are typically namespaced under `bitbybit.occt.*`, `bitbybit.manifold.*`, and `bitbybit.jscad.*`. - * **Drawing**: After creating a geometric entity, `bitbybit.draw.drawAnyAsync()` is used to render it into the ThreeJS scene. Different kernels might have slightly different drawing option DTOs (e.g., `DrawOcctShapeOptions`, `DrawManifoldOrCrossSectionOptions`, `DrawBasicGeometryOptions`). + * **Drawing**: After creating a geometric entity, `bitbybit.draw.drawAnyAsync()` is used to render it into the ThreeJS scene. Different kernels might have slightly different drawing option DTOs (e.g., `DrawOcctShapeOptions`, `DrawManifoldOrCrossSectionOptions`, `DrawBasicGeometryOptions`). The OCCT draw options now support additional properties like `edgeWidth`, `drawVertices`, `vertexSize`, and `vertexColour`. ## 5. Basic Styling (Optional) diff --git a/docs/learn/runners/engines/_category_.json b/docs/learn/runners/engines/_category_.json new file mode 100644 index 00000000..8e8dd647 --- /dev/null +++ b/docs/learn/runners/engines/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "Engines", + "position": 10, + "link": { + "type": "generated-index", + "description": "Learn how to use Bitbybit runners with different 3D engines - PlayCanvas, Three.js, and Babylon.js." + } +} diff --git a/docs/learn/runners/engines/babylonjs/_category_.json b/docs/learn/runners/engines/babylonjs/_category_.json new file mode 100644 index 00000000..c6eec2ad --- /dev/null +++ b/docs/learn/runners/engines/babylonjs/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "BabylonJS", + "position": 3, + "link": { + "type": "generated-index", + "description": "Learn how to use Bitbybit runners with Babylon.js engine." + } +} diff --git a/docs/learn/runners/engines/babylonjs/full-runner.mdx b/docs/learn/runners/engines/babylonjs/full-runner.mdx new file mode 100644 index 00000000..c2b5fa3f --- /dev/null +++ b/docs/learn/runners/engines/babylonjs/full-runner.mdx @@ -0,0 +1,203 @@ +--- +sidebar_position: 1 +title: BabylonJS Full Runner +description: Learn how to use the full version of the Bitbybit BabylonJS runner for CAD operations +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; + +# BabylonJS Full Runner + +The **full runner** bundles Babylon.js together with Bitbybit, so you don't need to load Babylon.js separately. This makes setup simpler but results in a larger bundle size. + +## Live Example + + + +## Complete Example + +Below is a complete example that creates a parametric lofted surface with rectangle holes using OCCT: + +```html + + + + + Bitbybit Runner BabylonJS Full Example + + + + + + + + + +
+ +
+ + + +``` + +## Key Points + +### Runner Script +```html + +``` + +The full runner includes BabylonJS, so you don't need to load it separately. + +### Using setTimeout +```javascript +setTimeout(async () => { + // Your code here +}, 0); +``` + +Unlike PlayCanvas and ThreeJS runners, the BabylonJS runner often uses `setTimeout` to ensure the canvas element is fully rendered before initialization. + +### Initialization +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, BABYLON, GUI, scene } = await runner.run(runnerOptions); +``` + +The `run()` method returns: +- `bitbybit` - The main Bitbybit API object +- `Bit` - Helper classes including input DTOs +- `BABYLON` - The BabylonJS library itself +- `GUI` - BabylonJS GUI library for UI elements +- `scene` - The BabylonJS Scene + +### Additional Options + +The BabylonJS runner supports additional options: + +| Option | Type | Description | +|--------|------|-------------| +| `canvasId` | string | The ID of your canvas element | +| `canvasZoneClass` | string | CSS class for the canvas container | +| `enableOCCT` | boolean | Enable OpenCASCADE kernel | +| `enableJSCAD` | boolean | Enable JSCAD kernel | +| `enableManifold` | boolean | Enable Manifold kernel | +| `enablePhysics` | boolean | Enable Havok physics | +| `enableKeyEventListeners` | boolean | Enable keyboard event listeners | +| `cameraPosition` | number[] | Initial camera position [x, y, z] | +| `cameraTarget` | number[] | Camera look-at target [x, y, z] | +| `backgroundColor` | string | Scene background color (hex) | +| `loadFonts` | string[] | Fonts to load for text operations | + +## Executing Rete Scripts + +You can also execute exported Rete scripts: + +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, BABYLON, GUI } = await runner.run(runnerOptions); + +// Execute an exported Rete script +runner.executeScript(exportedScriptString, { /* inputs */ }); +``` + +## GitHub Source + +View the full source code on GitHub: [BabylonJS Full Runner Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/babylon/full) diff --git a/docs/learn/runners/engines/babylonjs/lite-runner.mdx b/docs/learn/runners/engines/babylonjs/lite-runner.mdx new file mode 100644 index 00000000..9fdf91ea --- /dev/null +++ b/docs/learn/runners/engines/babylonjs/lite-runner.mdx @@ -0,0 +1,220 @@ +--- +sidebar_position: 2 +title: BabylonJS Lite Runner +description: Learn how to use the lite version of the Bitbybit BabylonJS runner for CAD operations +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; + +# BabylonJS Lite Runner + +The **lite runner** requires you to load Babylon.js separately before loading the runner. This results in a smaller Bitbybit bundle size and allows you to use your own version of Babylon.js. + +## Live Example + + + +## Complete Example + +Below is a complete example that creates a parametric lofted surface with rectangle holes using OCCT: + +```html + + + + + Bitbybit Runner BabylonJS Lite Example + + + + + + + + + + + + + + + + + + +
+ +
+ + + +``` + +## Key Points + +### Loading BabylonJS First + +```html + + + + + + +``` + +**Important:** You must load all required BabylonJS modules and expose the GUI to the window object before loading the lite runner. + +### Lite Runner Script +```html + +``` + +Note the `defer` attribute - this ensures the script loads after BabylonJS is available. + +### Initialization +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, camera, scene, renderer } = await runner.run(runnerOptions); +``` + +Access BabylonJS via the global `window.BABYLON` object since you loaded it separately. + +## Required BabylonJS Modules + +For full functionality, load these BabylonJS modules: + +| Module | Purpose | +|--------|---------| +| `babylon.js` | Core BabylonJS engine | +| `babylonjs.materials.min.js` | Material library | +| `babylonjs.loaders.min.js` | File loaders (GLTF, OBJ, etc.) | +| `babylonjs.serializers.min.js` | Export capabilities | +| `babylon.gui.min.js` | UI components | + +## When to Use Lite vs Full + +| Use Case | Recommended | +|----------|-------------| +| Quick prototyping | Full Runner | +| Need specific BabylonJS version | Lite Runner | +| Smallest possible bundle | Lite Runner | +| Using BabylonJS extensions | Lite Runner | +| Simple single-page demos | Full Runner | + +## Runner Options + +The options are the same as the full runner: + +| Option | Type | Description | +|--------|------|-------------| +| `canvasId` | string | The ID of your canvas element | +| `canvasZoneClass` | string | CSS class for the canvas container | +| `enableOCCT` | boolean | Enable OpenCASCADE kernel | +| `enableJSCAD` | boolean | Enable JSCAD kernel | +| `enableManifold` | boolean | Enable Manifold kernel | +| `cameraPosition` | number[] | Initial camera position [x, y, z] | +| `cameraTarget` | number[] | Camera look-at target [x, y, z] | +| `backgroundColor` | string | Scene background color (hex) | +| `loadFonts` | string[] | Fonts to load for text operations | + +## GitHub Source + +View the full source code on GitHub: [BabylonJS Lite Runner Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/babylon/lite) diff --git a/docs/learn/runners/engines/playcanvas/_category_.json b/docs/learn/runners/engines/playcanvas/_category_.json new file mode 100644 index 00000000..9ec3b125 --- /dev/null +++ b/docs/learn/runners/engines/playcanvas/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "PlayCanvas", + "position": 1, + "link": { + "type": "generated-index", + "description": "Learn how to use Bitbybit runners with PlayCanvas engine." + } +} diff --git a/docs/learn/runners/engines/playcanvas/full-runner.mdx b/docs/learn/runners/engines/playcanvas/full-runner.mdx new file mode 100644 index 00000000..19ae9d18 --- /dev/null +++ b/docs/learn/runners/engines/playcanvas/full-runner.mdx @@ -0,0 +1,194 @@ +--- +sidebar_position: 1 +title: PlayCanvas Full Runner +description: Learn how to use the full version of the Bitbybit PlayCanvas runner for CAD operations +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; + +# PlayCanvas Full Runner + +The **full runner** bundles PlayCanvas together with Bitbybit, so you don't need to load PlayCanvas separately. This makes setup simpler but results in a larger bundle size. + +## Live Example + + + +## Complete Example + +Below is a complete example that creates a parametric lofted surface with rectangle holes using OCCT: + +```html + + + + + Bitbybit Runner PlayCanvas Full Example + + + + + + + + + +
+ +
+ + + +``` + +## Key Points + +### Runner Script +```html + +``` + +The full runner includes PlayCanvas, so you don't need to load it separately. + +### Initialization +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, camera, scene, app, pc } = await runner.run(runnerOptions); +``` + +The `run()` method returns: +- `bitbybit` - The main Bitbybit API object +- `Bit` - Helper classes including input DTOs +- `camera` - The PlayCanvas camera entity +- `scene` - The PlayCanvas scene (root entity) +- `app` - The PlayCanvas Application instance +- `pc` - The PlayCanvas library itself + +### Runner Options + +| Option | Type | Description | +|--------|------|-------------| +| `canvasId` | string | The ID of your canvas element | +| `canvasZoneClass` | string | CSS class for the canvas container | +| `enableOCCT` | boolean | Enable OpenCASCADE kernel | +| `enableJSCAD` | boolean | Enable JSCAD kernel | +| `enableManifold` | boolean | Enable Manifold kernel | +| `cameraPosition` | number[] | Initial camera position [x, y, z] | +| `cameraTarget` | number[] | Camera look-at target [x, y, z] | +| `backgroundColor` | string | Scene background color (hex) | +| `loadFonts` | string[] | Fonts to load for text operations | + +## GitHub Source + +View the full source code on GitHub: [PlayCanvas Full Runner Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/playcanvas/full) diff --git a/docs/learn/runners/engines/playcanvas/lite-runner.mdx b/docs/learn/runners/engines/playcanvas/lite-runner.mdx new file mode 100644 index 00000000..1e64cdb4 --- /dev/null +++ b/docs/learn/runners/engines/playcanvas/lite-runner.mdx @@ -0,0 +1,217 @@ +--- +sidebar_position: 2 +title: PlayCanvas Lite Runner +description: Learn how to use the lite version of the Bitbybit PlayCanvas runner for CAD operations +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; + +# PlayCanvas Lite Runner + +The **lite runner** requires you to load PlayCanvas separately before loading the runner. This results in a smaller Bitbybit bundle size and allows you to use your own version of PlayCanvas. + +## Live Example + + + +## Complete Example + +Below is a complete example that creates a parametric lofted surface with rectangle holes using OCCT: + +```html + + + + + Bitbybit Runner PlayCanvas Lite Example + + + + + + + + + + + + + + +
+ +
+ + + +``` + +## Key Points + +### Loading PlayCanvas First +```html + + +``` + +**Important:** You must expose PlayCanvas to the window object as `window.PLAYCANVAS` before loading the lite runner. + +### Lite Runner Script +```html + +``` + +Note the `defer` attribute - this ensures the script loads after PlayCanvas is available. + +### Initialization +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, camera, scene, app, pc } = await runner.run(runnerOptions); +``` + +The returned objects are the same as the full runner. + +## When to Use Lite vs Full + +| Use Case | Recommended | +|----------|-------------| +| Quick prototyping | Full Runner | +| Need specific PlayCanvas version | Lite Runner | +| Smallest possible bundle | Lite Runner | +| Using other PlayCanvas modules | Lite Runner | +| Simple single-page demos | Full Runner | + +## Runner Options + +The options are the same as the full runner: + +| Option | Type | Description | +|--------|------|-------------| +| `canvasId` | string | The ID of your canvas element | +| `canvasZoneClass` | string | CSS class for the canvas container | +| `enableOCCT` | boolean | Enable OpenCASCADE kernel | +| `enableJSCAD` | boolean | Enable JSCAD kernel | +| `enableManifold` | boolean | Enable Manifold kernel | +| `cameraPosition` | number[] | Initial camera position [x, y, z] | +| `cameraTarget` | number[] | Camera look-at target [x, y, z] | +| `backgroundColor` | string | Scene background color (hex) | +| `loadFonts` | string[] | Fonts to load for text operations | + +## GitHub Source + +View the full source code on GitHub: [PlayCanvas Lite Runner Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/playcanvas/lite) diff --git a/docs/learn/runners/engines/threejs/_category_.json b/docs/learn/runners/engines/threejs/_category_.json new file mode 100644 index 00000000..f00bed8d --- /dev/null +++ b/docs/learn/runners/engines/threejs/_category_.json @@ -0,0 +1,8 @@ +{ + "label": "ThreeJS", + "position": 2, + "link": { + "type": "generated-index", + "description": "Learn how to use Bitbybit runners with Three.js engine." + } +} diff --git a/docs/learn/runners/engines/threejs/full-runner.mdx b/docs/learn/runners/engines/threejs/full-runner.mdx new file mode 100644 index 00000000..89fbf19a --- /dev/null +++ b/docs/learn/runners/engines/threejs/full-runner.mdx @@ -0,0 +1,206 @@ +--- +sidebar_position: 1 +title: ThreeJS Full Runner +description: Learn how to use the full version of the Bitbybit ThreeJS runner for CAD operations +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; + +# ThreeJS Full Runner + +The **full runner** bundles Three.js together with Bitbybit, so you don't need to load Three.js separately. This makes setup simpler but results in a larger bundle size. + +## Live Example + + + +## Complete Example + +Below is a complete example that creates a parametric lofted surface with rectangle holes using OCCT: + +```html + + + + + Bitbybit Runner ThreeJS Full Example + + + + + + + + + +
+ +
+ + + +``` + +## Key Points + +### Runner Script +```html + +``` + +The full runner includes ThreeJS, so you don't need to load it separately. + +### Initialization +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, camera, scene, renderer, THREEJS } = await runner.run(runnerOptions); +``` + +The `run()` method returns: +- `bitbybit` - The main Bitbybit API object +- `Bit` - Helper classes including input DTOs +- `camera` - The ThreeJS PerspectiveCamera +- `scene` - The ThreeJS Scene +- `renderer` - The ThreeJS WebGLRenderer +- `THREEJS` - The ThreeJS library itself + +### Custom Materials + +ThreeJS allows you to create custom materials for your CAD geometry: + +```javascript +const mat = new THREEJS.MeshPhongMaterial({ color: new THREEJS.Color('#6600ff') }); +mat.polygonOffset = true; +mat.polygonOffsetFactor = 1; +options.faceMaterial = mat; +``` + +### Runner Options + +| Option | Type | Description | +|--------|------|-------------| +| `canvasId` | string | The ID of your canvas element | +| `canvasZoneClass` | string | CSS class for the canvas container | +| `enableOCCT` | boolean | Enable OpenCASCADE kernel | +| `enableJSCAD` | boolean | Enable JSCAD kernel | +| `enableManifold` | boolean | Enable Manifold kernel | +| `cameraPosition` | number[] | Initial camera position [x, y, z] | +| `cameraTarget` | number[] | Camera look-at target [x, y, z] | +| `backgroundColor` | string | Scene background color (hex) | +| `loadFonts` | string[] | Fonts to load for text operations | + +## GitHub Source + +View the full source code on GitHub: [ThreeJS Full Runner Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/threejs/full) diff --git a/docs/learn/runners/engines/threejs/lite-runner.mdx b/docs/learn/runners/engines/threejs/lite-runner.mdx new file mode 100644 index 00000000..e48b39b3 --- /dev/null +++ b/docs/learn/runners/engines/threejs/lite-runner.mdx @@ -0,0 +1,241 @@ +--- +sidebar_position: 2 +title: ThreeJS Lite Runner +description: Learn how to use the lite version of the Bitbybit ThreeJS runner for CAD operations +--- + +import BitByBitRenderCanvas from '@site/src/components/BitByBitRenderCanvas'; + +# ThreeJS Lite Runner + +The **lite runner** requires you to load Three.js separately before loading the runner. This results in a smaller Bitbybit bundle size and allows you to use your own version of Three.js. + +## Live Example + + + +## Complete Example + +Below is a complete example that creates a parametric lofted surface with rectangle holes using OCCT: + +```html + + + + + Bitbybit Runner ThreeJS Lite Example + + + + + + + + + + + + + + +
+ +
+ + + +``` + +## Key Points + +### Loading ThreeJS First with Import Maps + +```html + + +``` + +**Important:** You must expose ThreeJS and OrbitControls to the window object before loading the lite runner: +- `window.THREEJS` - The ThreeJS library +- `window.OrbitControls` - The OrbitControls for camera navigation + +### Lite Runner Script +```html + +``` + +Note the `defer` attribute - this ensures the script loads after ThreeJS is available. + +### Initialization +```javascript +const runner = window.bitbybitRunner.getRunnerInstance(); +const { bitbybit, Bit, camera, scene, renderer } = await runner.run(runnerOptions); +``` + +**Note:** The lite runner returns `THREEJS` from the window object, not from the runner itself (since you loaded it separately). + +## When to Use Lite vs Full + +| Use Case | Recommended | +|----------|-------------| +| Quick prototyping | Full Runner | +| Need specific ThreeJS version | Lite Runner | +| Smallest possible bundle | Lite Runner | +| Using other ThreeJS addons | Lite Runner | +| Simple single-page demos | Full Runner | + +## Runner Options + +The options are the same as the full runner: + +| Option | Type | Description | +|--------|------|-------------| +| `canvasId` | string | The ID of your canvas element | +| `canvasZoneClass` | string | CSS class for the canvas container | +| `enableOCCT` | boolean | Enable OpenCASCADE kernel | +| `enableJSCAD` | boolean | Enable JSCAD kernel | +| `enableManifold` | boolean | Enable Manifold kernel | +| `cameraPosition` | number[] | Initial camera position [x, y, z] | +| `cameraTarget` | number[] | Camera look-at target [x, y, z] | +| `backgroundColor` | string | Scene background color (hex) | +| `loadFonts` | string[] | Fonts to load for text operations | + +## GitHub Source + +View the full source code on GitHub: [ThreeJS Lite Runner Examples](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner/threejs/lite) diff --git a/docs/learn/runners/intro-rete.mdx b/docs/learn/runners/intro-rete.mdx index fd2ea2d9..323f423e 100644 --- a/docs/learn/runners/intro-rete.mdx +++ b/docs/learn/runners/intro-rete.mdx @@ -14,7 +14,7 @@ import TabItem from '@theme/TabItem'; ## Make Your Own 3D Website -If you're looking for ways to create your own interactive 3D website, you're in the right place! This tutorial introduces you to **`bitbybit-runner.js`**, a powerful JavaScript library that hosts robust CAD (Computer-Aided Design) algorithms naturally embedded within the Babylon.js game engine. +If you're looking for ways to create your own interactive 3D website, you're in the right place! This tutorial introduces you to **`bitbybit-runner.js`**, a powerful JavaScript library that hosts robust CAD (Computer-Aided Design) algorithms naturally embedded within the BabylonJS game engine. We will guide you through: 1. Coding a simple visual script in Bitbybit to create a 3D cube. diff --git a/docs/learn/runners/intro.mdx b/docs/learn/runners/intro.mdx index b83e60ab..956b4394 100644 --- a/docs/learn/runners/intro.mdx +++ b/docs/learn/runners/intro.mdx @@ -2,7 +2,7 @@ sidebar_position: 1 title: Introducing Bitbybit Runner - Execute Visual 3D Scripts Anywhere sidebar_label: Bitbybit Runner -description: Discover Bitbybit Runner, a powerful tool to execute your Bitbybit 3D scripts on any website using a single JavaScript file, now with support for both BabylonJS and ThreeJS. +description: Discover Bitbybit Runner, a powerful tool to execute your Bitbybit 3D scripts on any website using a single JavaScript file, now with support for BabylonJS, ThreeJS, and PlayCanvas. tags: [getting-started, runners] --- @@ -33,15 +33,58 @@ This section will guide you through understanding and utilizing the Bitbybit Run The Bitbybit Runner is a single JavaScript file you can include in your website. It bundles all the open-source components of the Bitbybit platform, including CAD kernels (like OCCT, JSCAD) and various 3D algorithms, making it easier than ever to develop sophisticated parametric 3D experiences and model configurators directly in your web projects. Our journey with the Runner began with a version fully based on the **BabylonJS** game engine. This initial runner proved to be incredibly powerful, allowing direct execution of Bitbybit algorithms via JavaScript and enabling users to export scripts from our visual editors (Rete, Blockly, TypeScript) for use in any browser-based coding environment like StackBlitz, CodePen, JSFiddle, or their own websites. -* For more background, check out our original announcement: [Introducing BITBYBIT-RUNNER.JS](https://bitbybit.dev/blog/introducing-bitbybit-runner) +* For more background, check out our original announcement: [Introducing BITBYBIT-RUNNER.JS](/blog/introducing-bitbybit-runner) ![Robot running with lightning, symbolizing the power and speed of Bitbybit Runners.](https://ik.imagekit.io/bitbybit/app/assets/blog/updated-bitbybit-runners/updated-bitbybit-runners.jpeg "Runners are powerful, fast and amazing") -### New and Improved: ThreeJS Support & Lite Versions +## Two Ways to Use Bitbybit Runner -Responding to community feedback and our recent integration of the **ThreeJS** game engine, we are thrilled to announce: -1. **A Dedicated ThreeJS Runner:** This new runner follows the same principles as the BabylonJS version but is specifically designed to work with ThreeJS. It's a single JavaScript file that automatically loads all necessary resources, including web workers, CAD kernels, and other dependencies. -2. **"Lite" Versions for Both Engines:** We realized many projects already include ThreeJS or BabylonJS as a dependency. To avoid redundant loading and optimize performance, we've introduced **Lite versions** of our runners. These Lite bundles are significantly smaller as they do *not* include the game engine dependencies themselves, expecting you to load them separately in your project. This makes your website faster and more efficient if you're already using one of these engines. +The Bitbybit Runner is incredibly versatile and supports two distinct approaches to building 3D web applications: + +### 1. Execute Visual Scripts from Bitbybit Editors + +Export scripts created in our visual programming editors (Rete, Blockly, or TypeScript) and run them directly in your web applications. This approach is perfect for designers and developers who prefer visual programming or want to quickly prototype complex 3D models without writing code. + +**Learn more about this approach:** +- [Running Visual Scripts with Bitbybit Runner](/learn/runners/live-examples/static-3d-model-script) +- [Building a 3D Table Configurator (Rete)](/learn/runners/table-configurator-rete) +- [Building a 3D Table Configurator (Blockly)](/learn/runners/table-configurator-blockly) +- [Building a 3D Table Configurator (TypeScript)](/learn/runners/table-configurator-typescript) + +### 2. Write JavaScript Web Applications Directly + +Use the runner as a powerful JavaScript library to write 3D CAD applications from scratch. This approach gives you complete control over your application logic and is ideal for developers who want to build custom parametric 3D experiences, configurators, or tools using JavaScript. + +**Explore engine-specific guides and examples:** + + + + +- [PlayCanvas Full Runner Guide](/learn/runners/engines/playcanvas/full-runner) - Complete bundle with PlayCanvas included +- [PlayCanvas Lite Runner Guide](/learn/runners/engines/playcanvas/lite-runner) - Lightweight version for existing PlayCanvas projects + + + + +- [Three.js Full Runner Guide](/learn/runners/engines/threejs/full-runner) - Complete bundle with Three.js included +- [Three.js Lite Runner Guide](/learn/runners/engines/threejs/lite-runner) - Lightweight version for existing Three.js projects + + + + +- [Babylon.js Full Runner Guide](/learn/runners/engines/babylonjs/full-runner) - Complete bundle with Babylon.js included +- [Babylon.js Lite Runner Guide](/learn/runners/engines/babylonjs/lite-runner) - Lightweight version for existing Babylon.js projects + + + + +These guides include complete working examples with the `subdivideToRectangleHoles` algorithm, showing you how to create parametric lofted surfaces with rectangle hole patterns - a great starting point for understanding how to use Bitbybit's CAD capabilities in your JavaScript applications. + +### New and Improved: ThreeJS & PlayCanvas Support, Plus Lite Versions + +Responding to community feedback and our recent integration of the **ThreeJS** and **PlayCanvas** game engines, we are thrilled to announce: +1. **Dedicated ThreeJS and PlayCanvas Runners:** These new runners follow the same principles as the BabylonJS version but are specifically designed to work with ThreeJS and PlayCanvas respectively. Each is a single JavaScript file that automatically loads all necessary resources, including web workers, CAD kernels, and other dependencies. +2. **"Lite" Versions for All Three Engines:** We realized many projects already include ThreeJS, PlayCanvas, or BabylonJS as a dependency. To avoid redundant loading and optimize performance, we've introduced **Lite versions** of our runners. These Lite bundles are significantly smaller as they do *not* include the game engine dependencies themselves, expecting you to load them separately in your project. This makes your website faster and more efficient if you're already using one of these engines. ## Overview of Available Runners @@ -60,20 +103,32 @@ Here's a breakdown of the different Bitbybit Runner versions: * **`bitbybit-runner-lite-threejs.js`**: * A lightweight version that *excludes* the ThreeJS engine. * Expects `THREEJS` and `OrbitControls` global objects to be available on the `window` object. +* **`bitbybit-runner-playcanvas.js`**: + * Bundles the PlayCanvas game engine. + * Can execute Bitbybit's open-source 3D modeling algorithms and visual scripts, as long as they don't contain BabylonJS-specific logic. + * Ideal for high-performance applications, mobile optimization, and game development. +* **`bitbybit-runner-lite-playcanvas.js`**: + * A lightweight version that *excludes* the PlayCanvas engine. + * Expects `pc` (PlayCanvas) global object to be available on the `window` object. -### Key Differences: BabylonJS vs. ThreeJS Runners -Both runner families can execute all of Bitbybit's open-source 3D modeling algorithms and CAD kernels. The primary differences relate to the game engines: -* **Visual Script Compatibility:** If you're exporting scripts from our visual editors on bitbybit.dev (which are BabylonJS-based), the BabylonJS runner offers broader compatibility, especially if your scripts use BabylonJS-specific features (skyboxes, GUI, physics). -* **ThreeJS Runner for Visual Scripts:** The ThreeJS runner will attempt to run visual scripts but will throw an error if it encounters BabylonJS-specific logic. However, if your visual coding is limited to 3D modeling features, it should execute fine. -* **Direct JavaScript Coding:** If you're writing Bitbybit logic directly in JavaScript, either runner family will work well for accessing the core algorithms. -* **Bundle Size:** The BabylonJS runner file is generally larger than the ThreeJS one. Consider this when choosing. +### Key Differences: BabylonJS vs. ThreeJS vs. PlayCanvas Runners +All runner families can execute all of Bitbybit's open-source 3D modeling algorithms and CAD kernels. The primary differences relate to the game engines: +* **Visual Script Compatibility:** If you're exporting scripts from our visual editors on bitbybit.dev (which are BabylonJS-based), the BabylonJS runner offers broadest compatibility, especially if your scripts use BabylonJS-specific features (skyboxes, GUI, physics). +* **ThreeJS and PlayCanvas Runners for Visual Scripts:** The ThreeJS and PlayCanvas runners will attempt to run visual scripts but will throw an error if they encounter BabylonJS-specific logic. However, if your visual coding is limited to 3D modeling features, it should execute fine on all three engines. +* **Direct JavaScript Coding:** If you're writing Bitbybit logic directly in JavaScript, any runner family will work well for accessing the core algorithms. +* **Bundle Size:** The BabylonJS runner file is generally the largest, followed by ThreeJS, with PlayCanvas being highly optimized for performance and size. Consider this when choosing. +* **Performance Focus:** PlayCanvas is specifically optimized for high-performance applications and mobile devices, making it an excellent choice for game development and applications requiring smooth performance on all devices. -No matter your preference, we support both these fantastic game engines and are excited to see what you'll create! +No matter your preference, we support all three of these fantastic game engines and are excited to see what you'll create! ## Where to Find and How to Use the Runners The Bitbybit Runners are served from the **jsDelivr CDN**. Include them in your website using ` @@ -90,6 +145,14 @@ The Bitbybit Runners are served from the **jsDelivr CDN**. Include them in your ```html ``` +* **PlayCanvas Runner (Full):** + ```html + + ``` +* **PlayCanvas Runner (Lite):** + ```html + + ``` Once included, you can access the runner's functionality via the `window.bitbybitRunner` object. @@ -98,7 +161,7 @@ Once included, you can access the runner's functionality via the `window.bitbybi * **`run(options)`**: * An `async` function that initializes the runner. **You must `await` this function or use `.then()` before executing scripts.** * Requires an `options` object, minimally containing `canvasId: 'your-canvas-element-id'`. - * It creates and configures a default 3D scene (BabylonJS or ThreeJS depending on the runner), instantiates CAD kernels, loads WebAssembly libraries, fonts, physics engines (for BabylonJS runner), and other resources. + * It creates and configures a default 3D scene (BabylonJS, ThreeJS, or PlayCanvas depending on the runner), instantiates CAD kernels, loads WebAssembly libraries, fonts, physics engines (for BabylonJS runner), and other resources. * You can disable various features via the `options` object if not needed (e.g., `occt: false`, `jscad: false`). * **`executeScript(scriptContent, inputs)`**: * Executes a script (either JavaScript code you write or code exported from Bitbybit editors). @@ -135,7 +198,7 @@ Treat the exported script as a static code snippet; the Runner executes JavaScri To see the Bitbybit Runner in action, explore our example projects. These are often single `index.html` files you can open directly in your browser. -* ➡️ **[Example Use Cases of BabylonJS & ThreeJS Runners on GitHub](https://github.com/bitbybit-dev/app-examples/tree/main/runner)** +* ➡️ **[Example Use Cases of BabylonJS, ThreeJS & PlayCanvas Runners on GitHub](https://github.com/bitbybit-dev/bitbybit/tree/master/examples/runner)** This repository contains examples using both full and lite runners, demonstrating various scenarios, including: * Simply running an exported Rete script. * Coding Bitbybit logic directly in JavaScript using the runner. @@ -667,4 +730,4 @@ This configurator can even save STL and STEP files, allowing you to manufacture ## What's Next? -The Bitbybit Runner technology opens up vast possibilities. While this is an evolving tool, we're excited about its potential. We encourage you to experiment, provide feedback, and let us know how you're using it. More documentation, tutorials, and potentially a YouTube course are planned for the future. \ No newline at end of file +The Bitbybit Runner technology opens up vast possibilities. While this is an evolving tool, we're excited about its potential. We encourage you to experiment, provide feedback, and let us know how you're using it. More documentation and tutorials are planned for the future. \ No newline at end of file diff --git a/docs/learn/runners/live-examples/static-3d-model-script.mdx b/docs/learn/runners/live-examples/static-3d-model-script.mdx index 449631ae..ac5da726 100644 --- a/docs/learn/runners/live-examples/static-3d-model-script.mdx +++ b/docs/learn/runners/live-examples/static-3d-model-script.mdx @@ -26,7 +26,7 @@ Below is an interactive preview of the Rete visual program. You can see the node @@ -129,7 +129,7 @@ await runner.run(runnerOptions); // Asynchronously initializes the runner and Ba function exportedScript() { // This is the very long JavaScript string generated by "Export to Runner" from Rete. // It starts with "!async function(e,t,s,n,r){..." where e=BitByBit, t=bitbybit, etc. - return '{"type":"rete","version":"0.15.9","script":"!async function(e,t,s,n,r){let a={};a={x:[0],y:[0],z:[1],...a};const o=[{result:e.HS.executeBasedOnType(a,!1,(e=>t.vector.vectorXYZ(e))),transformers:[]}]; ... (rest of the very long exported JS string) ... }(BitByBit,bitbybit,bitbybitRunnerResult,bitbybitRunnerInputs,Bit);"}' + return '{"type":"rete","version":"0.21.0","script":"!async function(e,t,s,n,r){let a={};a={x:[0],y:[0],z:[1],...a};const o=[{result:e.HS.executeBasedOnType(a,!1,(e=>t.vector.vectorXYZ(e))),transformers:[]}]; ... (rest of the very long exported JS string) ... }(BitByBit,bitbybit,bitbybitRunnerResult,bitbybitRunnerInputs,Bit);"}' } // Step 4: Execute the Script diff --git a/docs/learn/tags.yml b/docs/learn/tags.yml index 3786e58b..e402b4e1 100644 --- a/docs/learn/tags.yml +++ b/docs/learn/tags.yml @@ -33,6 +33,11 @@ babylonjs: permalink: /babylonjs description: Babylon.js is a powerful, beautiful, simple, and open game and rendering engine packed into a friendly JavaScript framework. +playcanvas: + label: PlayCanvas + permalink: /playcanvas + description: PlayCanvas is a web-based game engine and development platform for building 3D applications. + occt: label: OCCT permalink: /occt diff --git a/docs/src/pages/index.module.css b/docs/src/pages/index.module.css index b90ae32b..8a61d36a 100644 --- a/docs/src/pages/index.module.css +++ b/docs/src/pages/index.module.css @@ -654,8 +654,10 @@ .techGrid { display: grid; - grid-template-columns: repeat(4, 1fr); + grid-template-columns: repeat(3, 1fr); gap: 1.5rem; + max-width: 1200px; + margin: 0 auto; } .techCard { @@ -673,6 +675,53 @@ text-decoration: none; } +.techCardEngine { + padding-top: 2rem; +} + +.techLogoContainer { + position: relative; + width: 80px; + height: 80px; + margin-bottom: 1rem; + display: flex; + align-items: center; + justify-content: center; +} + +.techLogoGlow { + position: absolute; + width: 120px; + height: 120px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + border-radius: 50%; + opacity: 0.6; + transition: all 0.4s ease; + filter: blur(20px); +} + +.techCard:hover .techLogoGlow { + opacity: 1; + transform: translate(-50%, -50%) scale(1.2); +} + +.techLogo { + width: 64px; + height: 64px; + object-fit: contain; + position: relative; + z-index: 1; + transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.3)); +} + +.techCard:hover .techLogo { + transform: scale(1.15) translateY(-4px); + filter: drop-shadow(0 8px 24px rgba(0, 0, 0, 0.4)); +} + .techBorder { position: absolute; top: 0; diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 3510d6ca..5bc1f0c8 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -63,24 +63,45 @@ const technologies = [ description: "Integrate Bitbybit's CAD capabilities with the popular Three.js rendering engine.", link: "/learn/npm-packages/threejs", color: "#049EF4", + logo: "https://bitbybit.dev/assets/threejs-logo.png", + isGameEngine: true, }, { name: "Babylon.js", description: "Build powerful 3D experiences combining Bitbybit geometry with Babylon.js features.", link: "/learn/npm-packages/babylonjs", color: "#BB464B", + logo: "https://bitbybit.dev/assets/babylon_logo.png", + isGameEngine: true, + }, + { + name: "PlayCanvas", + description: "Create high-performance 3D applications with Bitbybit and PlayCanvas game engine.", + link: "/learn/npm-packages/playcanvas", + color: "#FF6B35", + logo: "/img/playcanvas.png", + isGameEngine: true, + }, + { + name: "NPM Packages", + description: "Install and use Bitbybit in your own projects via NPM for complete flexibility.", + link: "/learn/npm-packages/intro", + color: "#CB3837", + isGameEngine: false, }, { name: "Shopify 3D Bits", description: "Add interactive 3D product configurators to your Shopify store with our app.", link: "/learn/3d-bits/intro", color: "#96BF48", + isGameEngine: false, }, { name: "Script Runners", description: "Execute visual scripts directly on your website without writing code.", link: "/learn/runners/intro", color: "#f0cebb", + isGameEngine: false, }, ]; @@ -241,12 +262,18 @@ function TechnologiesSection() {
Integrate With Your Stack -

Bitbybit works seamlessly with popular web technologies and platforms.

+

Bitbybit works with popular web technologies, game engines and e-commerce platforms.

{technologies.map((tech, idx) => ( - +
+ {tech.logo && ( +
+
+ {`${tech.name} +
+ )}

{tech.name}

{tech.description}

Learn more → diff --git a/docs/static/img/playcanvas.png b/docs/static/img/playcanvas.png new file mode 100644 index 00000000..23c3ac49 Binary files /dev/null and b/docs/static/img/playcanvas.png differ diff --git a/examples/angular/threejs/vite-basic-example/src/init-kernels.ts b/examples/angular/threejs/vite-basic-example/src/init-kernels.ts index 1741e4cb..11adfa00 100644 --- a/examples/angular/threejs/vite-basic-example/src/init-kernels.ts +++ b/examples/angular/threejs/vite-basic-example/src/init-kernels.ts @@ -4,122 +4,124 @@ import { BitByBitBase } from "@bitbybit-dev/threejs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; -import { firstValueFrom, first, tap } from "rxjs"; +import { firstValueFrom, first, map } from "rxjs"; export async function initWithKernels( - scene: Scene, - bitbybit: BitByBitBase, - options: KernelOptions -): Promise<{ message: string }> { - let occtWorkerInstance: Worker | undefined; - let jscadWorkerInstance: Worker | undefined; - let manifoldWorkerInstance: Worker | undefined; + scene: Scene, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string; initializedKernels: string[] }> { + let occtWorkerInstance: Worker | undefined; + let jscadWorkerInstance: Worker | undefined; + let manifoldWorkerInstance: Worker | undefined; - // 1. Conditionally create worker instances - if (options.enableOCCT) { - occtWorkerInstance = new Worker( - new URL("./workers/occt.worker.ts", import.meta.url), - { name: "OCC_WORKER", type: "module" } - ); - } - if (options.enableJSCAD) { - jscadWorkerInstance = new Worker( - new URL("./workers/jscad.worker.ts", import.meta.url), - { name: "JSCAD_WORKER", type: "module" } - ); - } - if (options.enableManifold) { - manifoldWorkerInstance = new Worker( - new URL("./workers/manifold.worker.ts", import.meta.url), - { name: "MANIFOLD_WORKER", type: "module" } + // 1. Conditionally create worker instances + if (options.enableOCCT) { + occtWorkerInstance = new Worker( + new URL("./workers/occt.worker.ts", import.meta.url), + { name: "OCC_WORKER", type: "module" } + ); + } + if (options.enableJSCAD) { + jscadWorkerInstance = new Worker( + new URL("./workers/jscad.worker.ts", import.meta.url), + { name: "JSCAD_WORKER", type: "module" } + ); + } + if (options.enableManifold) { + manifoldWorkerInstance = new Worker( + new URL("./workers/manifold.worker.ts", import.meta.url), + { name: "MANIFOLD_WORKER", type: "module" } + ); + } + + // 2. Initialize Bitbybit + await bitbybit.init( + scene, + occtWorkerInstance, + jscadWorkerInstance, + manifoldWorkerInstance ); - } - // 2. Initialize Bitbybit - await bitbybit.init( - scene, - occtWorkerInstance, - jscadWorkerInstance, - manifoldWorkerInstance - ); + // 3. Collect promises for kernel initializations + const initializationPromises: Promise[] = []; + let anyKernelSelectedForInit = false; - // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; - let anyKernelSelectedForInit = false; + if (options.enableOCCT) { + anyKernelSelectedForInit = true; + if (bitbybit.occtWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => "OCCT") + ) + ) + ); + } else { + console.warn( + "OCCT enabled in options, but occtWorkerManager not found after init." + ); + } + } - if (options.enableOCCT) { - anyKernelSelectedForInit = true; - if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) - ) - ).then(() => undefined) // Ensure the promise resolves to void for Promise.all - ); - } else { - console.warn( - "OCCT enabled in options, but occtWorkerManager not found after init." - ); + if (options.enableJSCAD) { + anyKernelSelectedForInit = true; + if (bitbybit.jscadWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => "JSCAD") + ) + ) + ); + } else { + console.warn( + "JSCAD enabled in options, but jscadWorkerManager not found after init." + ); + } } - } - if (options.enableJSCAD) { - anyKernelSelectedForInit = true; - if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) - ) - ).then(() => undefined) - ); - } else { - console.warn( - "JSCAD enabled in options, but jscadWorkerManager not found after init." - ); + if (options.enableManifold) { + anyKernelSelectedForInit = true; + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => "Manifold") + ) + ) + ); + } else { + console.warn( + "Manifold enabled in options, but manifoldWorkerManager not found after init." + ); + } } - } - if (options.enableManifold) { - anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) - ) - ).then(() => undefined) - ); - } else { - console.warn( - "Manifold enabled in options, but manifoldWorkerManager not found after init." - ); + // 4. Wait for selected & available kernels or handle no selection/availability + if (!anyKernelSelectedForInit) { + console.log("No kernels selected for initialization."); + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } - } - // 4. Wait for selected & available kernels or handle no selection/availability - if (!anyKernelSelectedForInit) { - console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; - } + if (initializationPromises.length === 0) { + // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) + console.log( + "Kernels were selected, but none had managers available for awaiting initialization." + ); + return { + message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], + }; + } - if (initializationPromises.length === 0) { - // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) - console.log( - "Kernels were selected, but none had managers available for awaiting initialization." - ); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected kernels were not awaitable for initialization state.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; - } - - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); - return { - message: "Selected and awaitable kernels initialized successfully.", - }; } diff --git a/examples/vite/babylonjs/hex-house-concept/src/helpers/init-kernels.ts b/examples/vite/babylonjs/hex-house-concept/src/helpers/init-kernels.ts index cb319e39..6115c54f 100644 --- a/examples/vite/babylonjs/hex-house-concept/src/helpers/init-kernels.ts +++ b/examples/vite/babylonjs/hex-house-concept/src/helpers/init-kernels.ts @@ -1,125 +1,127 @@ import type { BitByBitBase } from "@bitbybit-dev/babylonjs"; import type { Scene } from "@babylonjs/core"; import type { KernelOptions } from "../models"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; export async function initKernels( - scene: Scene, - bitbybit: BitByBitBase, - options: KernelOptions -): Promise<{ message: string }> { - let occtWorkerInstance: Worker | undefined; - let jscadWorkerInstance: Worker | undefined; - let manifoldWorkerInstance: Worker | undefined; + scene: Scene, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string; initializedKernels: string[] }> { + let occtWorkerInstance: Worker | undefined; + let jscadWorkerInstance: Worker | undefined; + let manifoldWorkerInstance: Worker | undefined; - // 1. Conditionally create worker instances - if (options.enableOCCT) { - occtWorkerInstance = new Worker( - new URL("../workers/occt.worker.ts", import.meta.url), - { name: "OCC_WORKER", type: "module" } - ); - } - if (options.enableJSCAD) { - jscadWorkerInstance = new Worker( - new URL("../workers/jscad.worker.ts", import.meta.url), - { name: "JSCAD_WORKER", type: "module" } - ); - } - if (options.enableManifold) { - manifoldWorkerInstance = new Worker( - new URL("../workers/manifold.worker.ts", import.meta.url), - { name: "MANIFOLD_WORKER", type: "module" } + // 1. Conditionally create worker instances + if (options.enableOCCT) { + occtWorkerInstance = new Worker( + new URL("../workers/occt.worker.ts", import.meta.url), + { name: "OCC_WORKER", type: "module" } + ); + } + if (options.enableJSCAD) { + jscadWorkerInstance = new Worker( + new URL("../workers/jscad.worker.ts", import.meta.url), + { name: "JSCAD_WORKER", type: "module" } + ); + } + if (options.enableManifold) { + manifoldWorkerInstance = new Worker( + new URL("../workers/manifold.worker.ts", import.meta.url), + { name: "MANIFOLD_WORKER", type: "module" } + ); + } + + // 2. Initialize Bitbybit + await bitbybit.init( + scene, + occtWorkerInstance, + jscadWorkerInstance, + manifoldWorkerInstance ); - } - // 2. Initialize Bitbybit - await bitbybit.init( - scene, - occtWorkerInstance, - jscadWorkerInstance, - manifoldWorkerInstance - ); + // 3. Collect promises for kernel initializations + const initializationPromises: Promise[] = []; + let anyKernelSelectedForInit = false; - // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; - let anyKernelSelectedForInit = false; + if (options.enableOCCT) { + anyKernelSelectedForInit = true; + if (bitbybit.occtWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => "OCCT") + ) + ) + ); + } else { + console.warn( + "OCCT enabled in options, but occtWorkerManager not found after init." + ); + } + } - if (options.enableOCCT) { - anyKernelSelectedForInit = true; - if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) - ) - ).then(() => {}) // Ensure the promise resolves to void for Promise.all - ); - } else { - console.warn( - "OCCT enabled in options, but occtWorkerManager not found after init." - ); + if (options.enableJSCAD) { + anyKernelSelectedForInit = true; + if (bitbybit.jscadWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => "JSCAD") + ) + ) + ); + } else { + console.warn( + "JSCAD enabled in options, but jscadWorkerManager not found after init." + ); + } } - } - if (options.enableJSCAD) { - anyKernelSelectedForInit = true; - if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "JSCAD enabled in options, but jscadWorkerManager not found after init." - ); + if (options.enableManifold) { + anyKernelSelectedForInit = true; + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => "Manifold") + ) + ) + ); + } else { + console.warn( + "Manifold enabled in options, but manifoldWorkerManager not found after init." + ); + } } - } - if (options.enableManifold) { - anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "Manifold enabled in options, but manifoldWorkerManager not found after init." - ); + // 4. Wait for selected & available kernels or handle no selection/availability + if (!anyKernelSelectedForInit) { + console.log("No kernels selected for initialization."); + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } - } - // 4. Wait for selected & available kernels or handle no selection/availability - if (!anyKernelSelectedForInit) { - console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; - } + if (initializationPromises.length === 0) { + // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) + console.log( + "Kernels were selected, but none had managers available for awaiting initialization." + ); + return { + message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], + }; + } - if (initializationPromises.length === 0) { - // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) - console.log( - "Kernels were selected, but none had managers available for awaiting initialization." - ); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected kernels were not awaitable for initialization state.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; - } - - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); - return { - message: "Selected and awaitable kernels initialized successfully.", - }; } diff --git a/examples/vite/babylonjs/hex-shell/src/helpers/init-kernels.ts b/examples/vite/babylonjs/hex-shell/src/helpers/init-kernels.ts index cb319e39..17fb7ade 100644 --- a/examples/vite/babylonjs/hex-shell/src/helpers/init-kernels.ts +++ b/examples/vite/babylonjs/hex-shell/src/helpers/init-kernels.ts @@ -1,125 +1,127 @@ import type { BitByBitBase } from "@bitbybit-dev/babylonjs"; import type { Scene } from "@babylonjs/core"; import type { KernelOptions } from "../models"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; export async function initKernels( - scene: Scene, - bitbybit: BitByBitBase, - options: KernelOptions -): Promise<{ message: string }> { - let occtWorkerInstance: Worker | undefined; - let jscadWorkerInstance: Worker | undefined; - let manifoldWorkerInstance: Worker | undefined; + scene: Scene, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string; initializedKernels: string[] }> { + let occtWorkerInstance: Worker | undefined; + let jscadWorkerInstance: Worker | undefined; + let manifoldWorkerInstance: Worker | undefined; - // 1. Conditionally create worker instances - if (options.enableOCCT) { - occtWorkerInstance = new Worker( - new URL("../workers/occt.worker.ts", import.meta.url), - { name: "OCC_WORKER", type: "module" } - ); - } - if (options.enableJSCAD) { - jscadWorkerInstance = new Worker( - new URL("../workers/jscad.worker.ts", import.meta.url), - { name: "JSCAD_WORKER", type: "module" } - ); - } - if (options.enableManifold) { - manifoldWorkerInstance = new Worker( - new URL("../workers/manifold.worker.ts", import.meta.url), - { name: "MANIFOLD_WORKER", type: "module" } + // 1. Conditionally create worker instances + if (options.enableOCCT) { + occtWorkerInstance = new Worker( + new URL("../workers/occt.worker.ts", import.meta.url), + { name: "OCC_WORKER", type: "module" } + ); + } + if (options.enableJSCAD) { + jscadWorkerInstance = new Worker( + new URL("../workers/jscad.worker.ts", import.meta.url), + { name: "JSCAD_WORKER", type: "module" } + ); + } + if (options.enableManifold) { + manifoldWorkerInstance = new Worker( + new URL("../workers/manifold.worker.ts", import.meta.url), + { name: "MANIFOLD_WORKER", type: "module" } + ); + } + + // 2. Initialize Bitbybit + await bitbybit.init( + scene, + occtWorkerInstance, + jscadWorkerInstance, + manifoldWorkerInstance ); - } - // 2. Initialize Bitbybit - await bitbybit.init( - scene, - occtWorkerInstance, - jscadWorkerInstance, - manifoldWorkerInstance - ); + // 3. Collect promises for kernel initializations + const initializationPromises: Promise[] = []; + let anyKernelSelectedForInit = false; - // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; - let anyKernelSelectedForInit = false; + if (options.enableOCCT) { + anyKernelSelectedForInit = true; + if (bitbybit.occtWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => "OCCT") + ) + ) + ); + } else { + console.warn( + "OCCT enabled in options, but occtWorkerManager not found after init." + ); + } + } - if (options.enableOCCT) { - anyKernelSelectedForInit = true; - if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) - ) - ).then(() => {}) // Ensure the promise resolves to void for Promise.all - ); - } else { - console.warn( - "OCCT enabled in options, but occtWorkerManager not found after init." - ); + if (options.enableJSCAD) { + anyKernelSelectedForInit = true; + if (bitbybit.jscadWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => "JSCAD") + ) + ) + ); + } else { + console.warn( + "JSCAD enabled in options, but jscadWorkerManager not found after init." + ); + } } - } - if (options.enableJSCAD) { - anyKernelSelectedForInit = true; - if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "JSCAD enabled in options, but jscadWorkerManager not found after init." - ); + if (options.enableManifold) { + anyKernelSelectedForInit = true; + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => "Manifold") + ) + ) + ); + } else { + console.warn( + "Manifold enabled in options, but manifoldWorkerManager not found after init." + ); + } } - } - if (options.enableManifold) { - anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "Manifold enabled in options, but manifoldWorkerManager not found after init." - ); + // 4. Wait for selected & available kernels or handle no selection/availability + if (!anyKernelSelectedForInit) { + console.log("No kernels selected for initialization."); + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } - } - // 4. Wait for selected & available kernels or handle no selection/availability - if (!anyKernelSelectedForInit) { - console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; - } + if (initializationPromises.length === 0) { + // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) + console.log( + "Kernels were selected, but none had managers available for awaiting initialization." + ); + return { + message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], + }; + } - if (initializationPromises.length === 0) { - // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) - console.log( - "Kernels were selected, but none had managers available for awaiting initialization." - ); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected kernels were not awaitable for initialization state.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; - } - - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); - return { - message: "Selected and awaitable kernels initialized successfully.", - }; -} +} \ No newline at end of file diff --git a/examples/vite/babylonjs/starter-template/src/main.ts b/examples/vite/babylonjs/starter-template/src/main.ts index d553fa2e..218cc848 100644 --- a/examples/vite/babylonjs/starter-template/src/main.ts +++ b/examples/vite/babylonjs/starter-template/src/main.ts @@ -13,7 +13,7 @@ import { DirectionalLight, Color4, } from "@babylonjs/core"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; // Define an interface for kernel options interface KernelOptions { @@ -121,7 +121,7 @@ async function initWithKernels( scene: Scene, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -155,7 +155,7 @@ async function initWithKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { @@ -165,9 +165,9 @@ async function initWithKernels( firstValueFrom( bitbybit.occtWorkerManager.occWorkerState$.pipe( first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) + map(() => "OCCT") ) - ).then(() => { }) // Ensure the promise resolves to void for Promise.all + ) ); } else { console.warn( @@ -183,9 +183,9 @@ async function initWithKernels( firstValueFrom( bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) + map(() => "JSCAD") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -196,14 +196,14 @@ async function initWithKernels( if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { initializationPromises.push( firstValueFrom( bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) + map(() => "Manifold") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -215,7 +215,7 @@ async function initWithKernels( // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } if (initializationPromises.length === 0) { @@ -225,13 +225,15 @@ async function initWithKernels( ); return { message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected and awaitable kernels initialized successfully.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; } diff --git a/examples/vite/playcanvas/hex-house-concept/src/helpers/init-kernels.ts b/examples/vite/playcanvas/hex-house-concept/src/helpers/init-kernels.ts index 4c03bcf4..11367ac8 100644 --- a/examples/vite/playcanvas/hex-house-concept/src/helpers/init-kernels.ts +++ b/examples/vite/playcanvas/hex-house-concept/src/helpers/init-kernels.ts @@ -1,127 +1,129 @@ import type { BitByBitBase } from "@bitbybit-dev/playcanvas"; import type { AppBase, Entity } from "playcanvas"; import type { KernelOptions } from "../models"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; export async function initKernels( - app: AppBase, - scene: Entity, - bitbybit: BitByBitBase, - options: KernelOptions -): Promise<{ message: string }> { - let occtWorkerInstance: Worker | undefined; - let jscadWorkerInstance: Worker | undefined; - let manifoldWorkerInstance: Worker | undefined; + app: AppBase, + scene: Entity, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string; initializedKernels: string[] }> { + let occtWorkerInstance: Worker | undefined; + let jscadWorkerInstance: Worker | undefined; + let manifoldWorkerInstance: Worker | undefined; - // 1. Conditionally create worker instances - if (options.enableOCCT) { - occtWorkerInstance = new Worker( - new URL("../workers/occt.worker.ts", import.meta.url), - { name: "OCC_WORKER", type: "module" } - ); - } - if (options.enableJSCAD) { - jscadWorkerInstance = new Worker( - new URL("../workers/jscad.worker.ts", import.meta.url), - { name: "JSCAD_WORKER", type: "module" } - ); - } - if (options.enableManifold) { - manifoldWorkerInstance = new Worker( - new URL("../workers/manifold.worker.ts", import.meta.url), - { name: "MANIFOLD_WORKER", type: "module" } + // 1. Conditionally create worker instances + if (options.enableOCCT) { + occtWorkerInstance = new Worker( + new URL("../workers/occt.worker.ts", import.meta.url), + { name: "OCC_WORKER", type: "module" } + ); + } + if (options.enableJSCAD) { + jscadWorkerInstance = new Worker( + new URL("../workers/jscad.worker.ts", import.meta.url), + { name: "JSCAD_WORKER", type: "module" } + ); + } + if (options.enableManifold) { + manifoldWorkerInstance = new Worker( + new URL("../workers/manifold.worker.ts", import.meta.url), + { name: "MANIFOLD_WORKER", type: "module" } + ); + } + + // 2. Initialize Bitbybit + await bitbybit.init( + app, + scene, + occtWorkerInstance, + jscadWorkerInstance, + manifoldWorkerInstance ); - } - // 2. Initialize Bitbybit - await bitbybit.init( - app, - scene, - occtWorkerInstance, - jscadWorkerInstance, - manifoldWorkerInstance - ); + // 3. Collect promises for kernel initializations + const initializationPromises: Promise[] = []; + let anyKernelSelectedForInit = false; - // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; - let anyKernelSelectedForInit = false; + if (options.enableOCCT) { + anyKernelSelectedForInit = true; + if (bitbybit.occtWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => "OCCT") + ) + ) + ); + } else { + console.warn( + "OCCT enabled in options, but occtWorkerManager not found after init." + ); + } + } - if (options.enableOCCT) { - anyKernelSelectedForInit = true; - if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) - ) - ).then(() => {}) // Ensure the promise resolves to void for Promise.all - ); - } else { - console.warn( - "OCCT enabled in options, but occtWorkerManager not found after init." - ); + if (options.enableJSCAD) { + anyKernelSelectedForInit = true; + if (bitbybit.jscadWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => "JSCAD") + ) + ) + ); + } else { + console.warn( + "JSCAD enabled in options, but jscadWorkerManager not found after init." + ); + } } - } - if (options.enableJSCAD) { - anyKernelSelectedForInit = true; - if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "JSCAD enabled in options, but jscadWorkerManager not found after init." - ); + if (options.enableManifold) { + anyKernelSelectedForInit = true; + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => "Manifold") + ) + ) + ); + } else { + console.warn( + "Manifold enabled in options, but manifoldWorkerManager not found after init." + ); + } } - } - if (options.enableManifold) { - anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "Manifold enabled in options, but manifoldWorkerManager not found after init." - ); + // 4. Wait for selected & available kernels or handle no selection/availability + if (!anyKernelSelectedForInit) { + console.log("No kernels selected for initialization."); + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } - } - // 4. Wait for selected & available kernels or handle no selection/availability - if (!anyKernelSelectedForInit) { - console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; - } + if (initializationPromises.length === 0) { + // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) + console.log( + "Kernels were selected, but none had managers available for awaiting initialization." + ); + return { + message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], + }; + } - if (initializationPromises.length === 0) { - // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) - console.log( - "Kernels were selected, but none had managers available for awaiting initialization." - ); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected kernels were not awaitable for initialization state.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; - } - - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); - return { - message: "Selected and awaitable kernels initialized successfully.", - }; } diff --git a/examples/vite/playcanvas/hex-shell/src/helpers/create-gui.ts b/examples/vite/playcanvas/hex-shell/src/helpers/create-gui.ts index 89ca242d..d823b843 100644 --- a/examples/vite/playcanvas/hex-shell/src/helpers/create-gui.ts +++ b/examples/vite/playcanvas/hex-shell/src/helpers/create-gui.ts @@ -103,7 +103,5 @@ export const createGui = ( }); gui.add(model, "update").name("Finalize"); - gui.add(model, "downloadSTL").name("Download STL"); gui.add(model, "downloadStep").name("Download STEP"); - gui.add(model, "downloadGLB").name("Download GLB"); }; diff --git a/examples/vite/playcanvas/hex-shell/src/helpers/init-kernels.ts b/examples/vite/playcanvas/hex-shell/src/helpers/init-kernels.ts index 9f6363c7..11367ac8 100644 --- a/examples/vite/playcanvas/hex-shell/src/helpers/init-kernels.ts +++ b/examples/vite/playcanvas/hex-shell/src/helpers/init-kernels.ts @@ -1,7 +1,7 @@ import type { BitByBitBase } from "@bitbybit-dev/playcanvas"; import type { AppBase, Entity } from "playcanvas"; import type { KernelOptions } from "../models"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; @@ -11,7 +11,7 @@ export async function initKernels( scene: Entity, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -46,7 +46,7 @@ export async function initKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { @@ -56,9 +56,9 @@ export async function initKernels( firstValueFrom( bitbybit.occtWorkerManager.occWorkerState$.pipe( first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) + map(() => "OCCT") ) - ).then(() => { }) // Ensure the promise resolves to void for Promise.all + ) ); } else { console.warn( @@ -74,9 +74,9 @@ export async function initKernels( firstValueFrom( bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) + map(() => "JSCAD") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -87,14 +87,14 @@ export async function initKernels( if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { initializationPromises.push( firstValueFrom( bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) + map(() => "Manifold") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -106,7 +106,7 @@ export async function initKernels( // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } if (initializationPromises.length === 0) { @@ -116,12 +116,14 @@ export async function initKernels( ); return { message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected and awaitable kernels initialized successfully.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; } diff --git a/examples/vite/playcanvas/starter-template/package-lock.json b/examples/vite/playcanvas/starter-template/package-lock.json new file mode 100644 index 00000000..e6c47df8 --- /dev/null +++ b/examples/vite/playcanvas/starter-template/package-lock.json @@ -0,0 +1,1380 @@ +{ + "name": "vite-playcanvas-starter", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vite-playcanvas-starter", + "version": "0.0.0", + "dependencies": { + "@bitbybit-dev/playcanvas": "0.21.0", + "playcanvas": "2.14.4" + }, + "devDependencies": { + "typescript": "~5.8.3", + "vite": "^6.3.2" + } + }, + "node_modules/@bitbybit-dev/base": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/base/-/base-0.21.0.tgz", + "integrity": "sha512-dNfsf2tu3/QUD1mvZzzTze8RwHyK2Jrt+Ary8EDKdNKMw+abtCpQYNerdrg0ZQPxvwu/OOQz6N1xCsCwsPkQJg==", + "license": "MIT" + }, + "node_modules/@bitbybit-dev/core": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/core/-/core-0.21.0.tgz", + "integrity": "sha512-IbeQ8ROreu0/RkkngUE+Eb19ecvqKOWhGefXvIqQmplxt3zEc0FI4Kw8wR3vYl2XMannuVt1xAIYtqqH0tjBzg==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/base": "0.21.0", + "@bitbybit-dev/jscad-worker": "0.21.0", + "@bitbybit-dev/manifold-worker": "0.21.0", + "@bitbybit-dev/occt-worker": "0.21.0", + "jsonpath-plus": "10.1.0", + "rxjs": "7.5.5", + "verb-nurbs-web": "2.1.3" + } + }, + "node_modules/@bitbybit-dev/jscad": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/jscad/-/jscad-0.21.0.tgz", + "integrity": "sha512-Q7eE2ILrJRstW4L1711aJZdpwPf1kuhZJqB+KLkkBLsneRsGsh82lBcCbVYAOMuF/q8Lnd4JHxPiIG/m+LYmPQ==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/base": "0.21.0", + "@jscad/3mf-serializer": "2.1.12", + "@jscad/dxf-serializer": "2.1.18", + "@jscad/io-utils": "2.0.28", + "@jscad/modeling": "2.12.3", + "@jscad/stl-serializer": "2.1.18" + } + }, + "node_modules/@bitbybit-dev/jscad-worker": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/jscad-worker/-/jscad-worker-0.21.0.tgz", + "integrity": "sha512-nIvd7GH5Gg51u6PmFXOADXVYTIvixTkNf0YapS1uix7D2JS9Mx10Z4wHAeRKzvJxUAAbEPPRFNkakE22HFGtLQ==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/jscad": "0.21.0", + "rxjs": "7.5.5" + } + }, + "node_modules/@bitbybit-dev/manifold": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/manifold/-/manifold-0.21.0.tgz", + "integrity": "sha512-DPpwK9ESB+auDeAbzXcfWRVEzWoMDkpkjc+/uwh+zuSiHSx92PSkzx1/LVTbQCsK+aOfOOy03HQCF5Bu2KQYtw==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/base": "0.21.0", + "manifold-3d": "3.3.2" + } + }, + "node_modules/@bitbybit-dev/manifold-worker": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/manifold-worker/-/manifold-worker-0.21.0.tgz", + "integrity": "sha512-6Cqv3BP+uI8mHKHI6Jx9TzCxRg1/kMHhCe18+B3KD/rTl9B5mnScCKRBnDansR3bH7aYZGnIw9PmiFnWLkQWEQ==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/manifold": "0.21.0", + "rxjs": "7.5.5" + } + }, + "node_modules/@bitbybit-dev/occt": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/occt/-/occt-0.21.0.tgz", + "integrity": "sha512-vPu3NmF1kBZMOkOdysolGfFxEEnwdHNGORoX3eWbMWT2JL/Tag5Q2CAc5jwa2VvZnS1nE/9x+DVlFQSo8LQnIg==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/base": "0.21.0" + } + }, + "node_modules/@bitbybit-dev/occt-worker": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/occt-worker/-/occt-worker-0.21.0.tgz", + "integrity": "sha512-xG1sgo6q/RdVA8AgG72wPe4qxcQlrO1V7/Ksn6MqIhZVTsJcm5RbVYlK+nziIWhvlMc1sKE0GNQFcPdALISLuQ==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/occt": "0.21.0", + "rxjs": "7.5.5" + } + }, + "node_modules/@bitbybit-dev/playcanvas": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@bitbybit-dev/playcanvas/-/playcanvas-0.21.0.tgz", + "integrity": "sha512-h9n4PAfh+ZUwT71FdDpx1zFOMsMjERL6UB6uY9NQ3bGROwF84RufvmGMxYYPLtkuqk1Es3Yj2LCD4DeV2kyyng==", + "license": "MIT", + "dependencies": { + "@bitbybit-dev/core": "0.21.0", + "playcanvas": "2.14.4" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.3", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gltf-transform/core": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "property-graph": "^3.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/donmccurdy" + } + }, + "node_modules/@gltf-transform/extensions": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "@gltf-transform/core": "^4.2.1", + "ktx-parse": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/donmccurdy" + } + }, + "node_modules/@gltf-transform/functions": { + "version": "4.2.1", + "license": "MIT", + "dependencies": { + "@gltf-transform/core": "^4.2.1", + "@gltf-transform/extensions": "^4.2.1", + "ktx-parse": "^1.0.1", + "ndarray": "^1.0.19", + "ndarray-lanczos": "^0.3.0", + "ndarray-pixels": "^5.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/donmccurdy" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jscad/3mf-serializer": { + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@jscad/3mf-serializer/-/3mf-serializer-2.1.12.tgz", + "integrity": "sha512-+rxAIKIHCpaplupwwsdXtG1IdimPXFFB45nrjy6gdFHi36bEQW6y/RQD/ngenRiKnYSxu7/M0LatHcjve8z64A==", + "license": "MIT", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.3", + "fflate": "0.7.3", + "onml": "1.2.0" + } + }, + "node_modules/@jscad/array-utils": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@jscad/array-utils/-/array-utils-2.1.4.tgz", + "integrity": "sha512-c31r4zSKsE+4Xfwk2V8monDA0hx5G89QGzaakWVUvuGNowYS9WSsYCwHiTIXodjR+HEnDu4okQ7k/whmP0Ne2g==", + "license": "MIT" + }, + "node_modules/@jscad/dxf-serializer": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@jscad/dxf-serializer/-/dxf-serializer-2.1.18.tgz", + "integrity": "sha512-T5Qe2jbEphcWAk7GaOY8PCMD4DPhTm6gWk/MPJCExphhnUwJqcU/1RiMb5hiD+N6hHzmlxD59I8QTjlp9LbLSA==", + "license": "MIT", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.3" + } + }, + "node_modules/@jscad/io-utils": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/@jscad/io-utils/-/io-utils-2.0.28.tgz", + "integrity": "sha512-spXh37wAgmwjKztoH/HANLgImcqRHX5+H/cRIxPfpqDIWvu7I5bRg2ZTwh25SYlKIzxPk4qX5Nu7Ax5+Kg+QXQ==", + "license": "MIT" + }, + "node_modules/@jscad/modeling": { + "version": "2.12.3", + "resolved": "https://registry.npmjs.org/@jscad/modeling/-/modeling-2.12.3.tgz", + "integrity": "sha512-DnAacXq3zhlYWIixGlFD7RMpgZAMuCaMZNQov0NaoFfs2GaBuTC5eqkqKcEVbhEerBmTllbjjF5IXjJMt9jcOA==", + "license": "MIT" + }, + "node_modules/@jscad/stl-serializer": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/@jscad/stl-serializer/-/stl-serializer-2.1.18.tgz", + "integrity": "sha512-pz++TRjHI7jBPnnxQUi4B/uYUG3yCDsKlw8+xL2kumwjb+auc6Ng6Rnr/GqUTkgHrnGut08wg/8AekyWjvBwYg==", + "license": "MIT", + "dependencies": { + "@jscad/array-utils": "2.1.4", + "@jscad/modeling": "2.12.3" + } + }, + "node_modules/@jscadui/3mf-export": { + "version": "0.5.0", + "license": "MIT" + }, + "node_modules/@jsep-plugin/assignment": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jsep-plugin/assignment/-/assignment-1.3.0.tgz", + "integrity": "sha512-VVgV+CXrhbMI3aSusQyclHkenWSAm95WaiKrMxRFam3JSUiIaQjoMIw2sEs/OX4XifnqeQUN4DYbJjlA8EfktQ==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@jsep-plugin/regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@jsep-plugin/regex/-/regex-1.0.4.tgz", + "integrity": "sha512-q7qL4Mgjs1vByCaTnDFcBnV9HS7GVPJX5vyVoCgZHNSC9rjwIlmbXG5sUuorR5ndfHAIlJ8pVStxvjXHbNvtUg==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + }, + "peerDependencies": { + "jsep": "^0.4.0||^1.0.0" + } + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.1", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/ndarray": { + "version": "1.0.14", + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@webgpu/types": { + "version": "0.1.68", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.68.tgz", + "integrity": "sha512-3ab1B59Ojb6RwjOspYLsTpCzbNB3ZaamIAxBMmvnNkiDoLTZUOBXZ9p5nAYVEkQlDdf6qAZWi1pqj9+ypiqznA==", + "license": "BSD-3-Clause" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/canvas": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.0.tgz", + "integrity": "sha512-jk0GxrLtUEmW/TmFsk2WghvgHe8B0pxGilqCL21y8lHkPUGa6FTsnCNtHPOzT8O3y+N+m3espawV80bbBlgfTA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC", + "optional": true + }, + "node_modules/commander": { + "version": "13.1.0", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/core-js": { + "version": "3.47.0", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cwise-compiler": { + "version": "1.1.3", + "license": "MIT", + "dependencies": { + "uniq": "^1.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/esbuild": { + "version": "0.25.3", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.3", + "@esbuild/android-arm": "0.25.3", + "@esbuild/android-arm64": "0.25.3", + "@esbuild/android-x64": "0.25.3", + "@esbuild/darwin-arm64": "0.25.3", + "@esbuild/darwin-x64": "0.25.3", + "@esbuild/freebsd-arm64": "0.25.3", + "@esbuild/freebsd-x64": "0.25.3", + "@esbuild/linux-arm": "0.25.3", + "@esbuild/linux-arm64": "0.25.3", + "@esbuild/linux-ia32": "0.25.3", + "@esbuild/linux-loong64": "0.25.3", + "@esbuild/linux-mips64el": "0.25.3", + "@esbuild/linux-ppc64": "0.25.3", + "@esbuild/linux-riscv64": "0.25.3", + "@esbuild/linux-s390x": "0.25.3", + "@esbuild/linux-x64": "0.25.3", + "@esbuild/netbsd-arm64": "0.25.3", + "@esbuild/netbsd-x64": "0.25.3", + "@esbuild/openbsd-arm64": "0.25.3", + "@esbuild/openbsd-x64": "0.25.3", + "@esbuild/sunos-x64": "0.25.3", + "@esbuild/win32-arm64": "0.25.3", + "@esbuild/win32-ia32": "0.25.3", + "@esbuild/win32-x64": "0.25.3" + } + }, + "node_modules/esbuild-plugin-text-replace": { + "version": "1.3.0", + "license": "bsd-2-clause", + "dependencies": { + "ts-replace-all": "^1.0.0" + }, + "engines": { + "node": ">=10.1.0" + } + }, + "node_modules/esbuild-wasm": { + "version": "0.25.12", + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fdir": { + "version": "6.4.4", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fflate": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.7.3.tgz", + "integrity": "sha512-0Zz1jOzJWERhyhsimS54VTqOteCNwRtIlh8isdL0AXLo0g7xNTfTL7oWrkmCnPhZGocKIkWHBistBrrpoNH3aw==", + "license": "MIT" + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "optional": true + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT", + "optional": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", + "optional": true + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause", + "optional": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC", + "optional": true + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", + "optional": true + }, + "node_modules/iota-array": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "license": "MIT" + }, + "node_modules/jsep": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsep/-/jsep-1.4.0.tgz", + "integrity": "sha512-B7qPcEVE3NVkmSJbaYxvv4cHkVW7DQsZz13pUMrfS8z8Q/BuShN+gcTXrUlPiGqM2/t/EEaI030bpxMqY8gMlw==", + "license": "MIT", + "engines": { + "node": ">= 10.16.0" + } + }, + "node_modules/jsonpath-plus": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.1.0.tgz", + "integrity": "sha512-gHfV1IYqH8uJHYVTs8BJX1XKy2/rR93+f8QQi0xhx95aCiXn1ettYAd5T+7FU6wfqyDoX/wy0pm/fL3jOKJ9Lg==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.2.1", + "@jsep-plugin/regex": "^1.0.3", + "jsep": "^1.3.9" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/ktx-parse": { + "version": "1.1.0", + "license": "MIT" + }, + "node_modules/manifold-3d": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/manifold-3d/-/manifold-3d-3.3.2.tgz", + "integrity": "sha512-Xx+S7kkbqlZxSPfBKH5yVKDO6k/eW7JRvnG1dKCFO0D4zjifOV5GM18TR0sREDcbKAzglHZ5OoxxpZRkxg6U5A==", + "license": "Apache-2.0", + "dependencies": { + "@gltf-transform/core": "^4.2.0", + "@gltf-transform/extensions": "^4.2.0", + "@gltf-transform/functions": "^4.2.0", + "@jridgewell/trace-mapping": "^0.3.31", + "@jscadui/3mf-export": "^0.5.0", + "commander": "^13.1.0", + "convert-source-map": "^2.0.0", + "esbuild-plugin-text-replace": "^1.3.0", + "esbuild-wasm": "^0.25.11", + "fflate": "^0.8.0" + }, + "bin": { + "manifold-cad": "bin/manifold-cad" + } + }, + "node_modules/manifold-3d/node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT", + "optional": true + }, + "node_modules/nan": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz", + "integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==", + "license": "MIT", + "optional": true + }, + "node_modules/nanoid": { + "version": "3.3.11", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT", + "optional": true + }, + "node_modules/ndarray": { + "version": "1.0.19", + "license": "MIT", + "dependencies": { + "iota-array": "^1.0.0", + "is-buffer": "^1.0.2" + } + }, + "node_modules/ndarray-lanczos": { + "version": "0.3.0", + "license": "MIT", + "dependencies": { + "@types/ndarray": "^1.0.11", + "ndarray": "^1.0.19" + } + }, + "node_modules/ndarray-ops": { + "version": "1.2.2", + "license": "MIT", + "dependencies": { + "cwise-compiler": "^1.0.0" + } + }, + "node_modules/ndarray-pixels": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "@types/ndarray": "^1.0.14", + "ndarray": "^1.0.19", + "ndarray-ops": "^1.2.2", + "sharp": "^0.34.0" + } + }, + "node_modules/node-abi": { + "version": "3.85.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", + "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "optional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onml": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/onml/-/onml-1.2.0.tgz", + "integrity": "sha512-olqYAg18XoHAhm7tK9DdBCOVdts70DGmMgCNLOWyqZokht2utgGSKBB4JHi6pBZpmioAhcYlxK+91L3tsrz+GA==", + "license": "MIT", + "dependencies": { + "sax": "^1.2.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/playcanvas": { + "version": "2.14.4", + "license": "MIT", + "dependencies": { + "@types/webxr": "^0.5.24", + "@webgpu/types": "^0.1.66" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "canvas": "3.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.3", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/property-graph": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rollup": { + "version": "4.40.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.1", + "@rollup/rollup-android-arm64": "4.40.1", + "@rollup/rollup-darwin-arm64": "4.40.1", + "@rollup/rollup-darwin-x64": "4.40.1", + "@rollup/rollup-freebsd-arm64": "4.40.1", + "@rollup/rollup-freebsd-x64": "4.40.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.1", + "@rollup/rollup-linux-arm-musleabihf": "4.40.1", + "@rollup/rollup-linux-arm64-gnu": "4.40.1", + "@rollup/rollup-linux-arm64-musl": "4.40.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-gnu": "4.40.1", + "@rollup/rollup-linux-riscv64-musl": "4.40.1", + "@rollup/rollup-linux-s390x-gnu": "4.40.1", + "@rollup/rollup-linux-x64-gnu": "4.40.1", + "@rollup/rollup-linux-x64-musl": "4.40.1", + "@rollup/rollup-win32-arm64-msvc": "4.40.1", + "@rollup/rollup-win32-ia32-msvc": "4.40.1", + "@rollup/rollup-win32-x64-msvc": "4.40.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rxjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz", + "integrity": "sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" + }, + "node_modules/semver": { + "version": "7.7.3", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.13", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/ts-replace-all": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "core-js": "^3.4.1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uniq": { + "version": "1.0.1", + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT", + "optional": true + }, + "node_modules/verb-nurbs-web": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/verb-nurbs-web/-/verb-nurbs-web-2.1.3.tgz", + "integrity": "sha512-2PvI2bx7dn0r3kWtk+JuDIDZ+p7I5Piu8y6/ZNhUVpilOyHKK1nNzLHtgown+dFOmBnKnuAKIMh1xn/5kzrxZA==", + "license": "MIT", + "optionalDependencies": { + "webworker-threads": "^0.7.12" + } + }, + "node_modules/vite": { + "version": "6.3.4", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/webworker-threads": { + "version": "0.7.17", + "resolved": "https://registry.npmjs.org/webworker-threads/-/webworker-threads-0.7.17.tgz", + "integrity": "sha512-Y2w2aXBbDLk9IzTEb9u+MsODC3s4YlGI7g4h0t+1OAwIO8yBI9rQL35ZYlyayiCuWu1dZMH/P7kGU8OwW7YsyA==", + "hasInstallScript": true, + "license": "(MIT AND Apache-2.0)", + "optional": true, + "dependencies": { + "bindings": "^1.3.0", + "nan": "^2.11.0" + }, + "engines": { + "node": ">= 0.10.16" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC", + "optional": true + } + } +} diff --git a/examples/vite/playcanvas/starter-template/src/main.ts b/examples/vite/playcanvas/starter-template/src/main.ts index 35889ecd..df4f4ded 100644 --- a/examples/vite/playcanvas/starter-template/src/main.ts +++ b/examples/vite/playcanvas/starter-template/src/main.ts @@ -4,7 +4,7 @@ import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; import { Application, Color, Entity, FILLMODE_FILL_WINDOW, Mouse, RESOLUTION_AUTO, TouchDevice } from "playcanvas"; // Define an interface for kernel options @@ -125,7 +125,7 @@ async function initWithKernels( scene: pc.Entity, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -160,7 +160,7 @@ async function initWithKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { @@ -170,9 +170,9 @@ async function initWithKernels( firstValueFrom( bitbybit.occtWorkerManager.occWorkerState$.pipe( first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) + map(() => "OCCT") ) - ).then(() => undefined) + ) ); } else { console.warn( @@ -188,9 +188,9 @@ async function initWithKernels( firstValueFrom( bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) + map(() => "JSCAD") ) - ).then(() => undefined) + ) ); } else { console.warn( @@ -201,14 +201,14 @@ async function initWithKernels( if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { initializationPromises.push( firstValueFrom( bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) + map(() => "Manifold") ) - ).then(() => undefined) + ) ); } else { console.warn( @@ -220,7 +220,7 @@ async function initWithKernels( // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } if (initializationPromises.length === 0) { @@ -230,13 +230,15 @@ async function initWithKernels( ); return { message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected and awaitable kernels initialized successfully.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; } diff --git a/examples/vite/threejs/cup/src/create-cup.ts b/examples/vite/threejs/cup/src/create-cup.ts index 7858de2f..f8a0f14f 100644 --- a/examples/vite/threejs/cup/src/create-cup.ts +++ b/examples/vite/threejs/cup/src/create-cup.ts @@ -1,8 +1,8 @@ -import type { BitByBitBase } from '@bitbybit-dev/threejs'; -import { Color, MeshPhongMaterial, Scene } from 'three'; -import { Inputs } from '@bitbybit-dev/threejs'; -import type { Current } from './models/current'; -import type { Model } from './models/model'; +import type { BitByBitBase } from "@bitbybit-dev/threejs"; +import { Color, MeshPhongMaterial, Scene } from "three"; +import { Inputs } from "@bitbybit-dev/threejs"; +import type { Current } from "./models/current"; +import type { Model } from "./models/model"; export const createShape = async ( bitbybit: BitByBitBase | undefined, @@ -16,7 +16,7 @@ export const createShape = async ( await bitbybit.occt.deleteShapes({ shapes: shapesToClean }); } - const faceColour = '#1c224f'; + const faceColour = "#1c224f"; const roundingRadius = model.cupThickness / 3; const cupHolderLength = 2; @@ -30,7 +30,7 @@ export const createShape = async ( }); const cupHolderWidth = model.cupHandleDistance + model.cupThickness * 2; - const edgeColour = '#ffffff'; + const edgeColour = "#ffffff"; const box = await bitbybit.occt.shapes.solid.createBox({ width: cupHolderWidth * 2, @@ -81,7 +81,7 @@ export const createShape = async ( }); const dimOpt = new Inputs.OCCT.SimpleLinearLengthDimensionDto(); - dimOpt.labelSuffix = '(cm)'; + dimOpt.labelSuffix = "(cm)"; dimOpt.start = [model.cupRadius, 0, 0]; dimOpt.end = [-model.cupRadius, 0, 0]; dimOpt.labelSize = 0.5; @@ -118,7 +118,8 @@ export const createShape = async ( options.edgeOpacity = 1; options.drawEdges = true; options.edgeColour = edgeColour; - options.precision = 0.05; + options.drawTwoSided = false; + options.precision = 0.01; const mat = new MeshPhongMaterial({ color: new Color(model.color) }); mat.polygonOffset = true; diff --git a/examples/vite/threejs/cup/src/main.ts b/examples/vite/threejs/cup/src/main.ts index be06906a..4450b83b 100644 --- a/examples/vite/threejs/cup/src/main.ts +++ b/examples/vite/threejs/cup/src/main.ts @@ -1,9 +1,9 @@ -import './style.css'; -import { BitByBitBase } from '@bitbybit-dev/threejs'; -import { OccStateEnum } from '@bitbybit-dev/occt-worker'; -import { Inputs } from '@bitbybit-dev/threejs'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; -import { STLExporter } from 'three/examples/jsm/exporters/STLExporter'; +import "./style.css"; +import { BitByBitBase } from "@bitbybit-dev/threejs"; +import { OccStateEnum } from "@bitbybit-dev/occt-worker"; +import { Inputs } from "@bitbybit-dev/threejs"; +import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; +import { STLExporter } from "three/examples/jsm/exporters/STLExporter"; import { Color, DirectionalLight, @@ -17,13 +17,13 @@ import { Vector3, VSMShadowMap, WebGLRenderer, -} from 'three'; -import { GUI } from 'lil-gui'; -import { createShape } from './create-cup'; -import type { Current } from './models/current'; -import type { Model } from './models/model'; +} from "three"; +import { GUI } from "lil-gui"; +import { createShape } from "./create-cup"; +import type { Current } from "./models/current"; +import type { Model } from "./models/model"; -document.querySelector('#app')!.innerHTML = ` +document.querySelector("#app")!.innerHTML = ` `; @@ -41,7 +41,7 @@ function component() { cupThickness: 1, cupHandleDistance: 2, cupHandleHeight: 0.5, - color: '#091044', + color: "#091044", } as Model; let shapesToClean: Inputs.OCCT.TopoDSShapePointer[] = []; @@ -60,7 +60,7 @@ function component() { if (bitbybit && finalShape) { await bitbybit.occt.io.saveShapeSTEP({ shape: finalShape, - fileName: 'shape', + fileName: "shape", adjustYtoZ: true, tryDownload: true, }); @@ -71,12 +71,12 @@ function component() { if (scene) { var exporter = new STLExporter(); var str = exporter.parse(scene); - var blob = new Blob([str], { type: 'text/plain' }); - var link = document.createElement('a'); - link.style.display = 'none'; + var blob = new Blob([str], { type: "text/plain" }); + var link = document.createElement("a"); + link.style.display = "none"; document.body.appendChild(link); link.href = URL.createObjectURL(blob); - link.download = 'Scene.stl'; + link.download = "Scene.stl"; link.click(); } }; @@ -103,11 +103,11 @@ function component() { bitbybit = new BitByBitBase(); const domNode = document.getElementById( - 'three-canvas' + "three-canvas" ) as HTMLCanvasElement; - const occt = new Worker(new URL('./workers/occt.worker', import.meta.url), { - name: 'OCC', - type: 'module', + const occt = new Worker(new URL("./workers/occt.worker", import.meta.url), { + name: "OCC", + type: "module", }); const camera = new PerspectiveCamera( @@ -152,7 +152,7 @@ function component() { camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); }; - window.addEventListener('resize', onWindowResize, false); + window.addEventListener("resize", onWindowResize, false); renderer.setClearColor(new Color(0x1a1c1f), 1); @@ -202,75 +202,75 @@ function component() { }; const disableGUI = () => { - const lilGui = document.getElementsByClassName('lil-gui')[0] as HTMLElement; - lilGui.style.pointerEvents = 'none'; - lilGui.style.opacity = '0.5'; + const lilGui = document.getElementsByClassName("lil-gui")[0] as HTMLElement; + lilGui.style.pointerEvents = "none"; + lilGui.style.opacity = "0.5"; }; const enableGUI = () => { - const lilGui = document.getElementsByClassName('lil-gui')[0] as HTMLElement; - lilGui.style.pointerEvents = 'all'; - lilGui.style.opacity = '1'; + const lilGui = document.getElementsByClassName("lil-gui")[0] as HTMLElement; + lilGui.style.pointerEvents = "all"; + lilGui.style.opacity = "1"; }; const createGui = () => { const gui = new GUI(); current.gui = gui; - gui.$title.innerHTML = 'Cup'; + gui.$title.innerHTML = "Cup"; gui - .add(model, 'cupHeight', 6, 16, 0.01) - .name('Height') + .add(model, "cupHeight", 6, 16, 0.01) + .name("Height") .onFinishChange((value: number) => { model.cupHeight = value; updateShape(); }); gui - .add(model, 'cupRadius', 3, 8, 0.01) - .name('Radius') + .add(model, "cupRadius", 3, 8, 0.01) + .name("Radius") .onFinishChange((value: number) => { model.cupRadius = value; updateShape(); }); gui - .add(model, 'cupThickness', 0.5, 1, 0.01) - .name('Thickness') + .add(model, "cupThickness", 0.5, 1, 0.01) + .name("Thickness") .onFinishChange((value: number) => { model.cupThickness = value; updateShape(); }); gui - .add(model, 'cupHandleDistance', 0.3, 3, 0.01) - .name('Handle Distance') + .add(model, "cupHandleDistance", 0.3, 3, 0.01) + .name("Handle Distance") .onFinishChange((value: number) => { model.cupHandleDistance = value; updateShape(); }); gui - .add(model, 'cupHandleHeight', 0, 1, 0.01) - .name('Handle Height') + .add(model, "cupHandleHeight", 0, 1, 0.01) + .name("Handle Height") .onFinishChange((value: number) => { model.cupHandleHeight = value; updateShape(); }); gui - .addColor(model, 'color') - .name('Color') + .addColor(model, "color") + .name("Color") .onChange((value: string) => { const children = current.group?.children[0].children as Mesh[]; [...children, current.ground].forEach((child) => { const material = (child as Mesh).material as MeshPhongMaterial; - material.color.setHex(parseInt(value.replace('#', '0x'))); + material.color.setHex(parseInt(value.replace("#", "0x"))); }); }); - gui.add(model, 'downloadSTL').name('Download STL'); - gui.add(model, 'downloadStep').name('Download STEP'); + gui.add(model, "downloadSTL").name("Download STL"); + gui.add(model, "downloadStep").name("Download STEP"); }; init(); diff --git a/examples/vite/threejs/cup/src/models/current.ts b/examples/vite/threejs/cup/src/models/current.ts index cb3e00e3..0d2eacd9 100644 --- a/examples/vite/threejs/cup/src/models/current.ts +++ b/examples/vite/threejs/cup/src/models/current.ts @@ -1,5 +1,5 @@ -import { Group, Mesh } from 'three'; -import { GUI } from 'lil-gui'; +import { Group, Mesh } from "three"; +import { GUI } from "lil-gui"; export type Current = { group: Group | undefined; diff --git a/examples/vite/threejs/cup/src/workers/jscad.worker.ts b/examples/vite/threejs/cup/src/workers/jscad.worker.ts index 5ee5cda1..eeed36fd 100644 --- a/examples/vite/threejs/cup/src/workers/jscad.worker.ts +++ b/examples/vite/threejs/cup/src/workers/jscad.worker.ts @@ -1,12 +1,12 @@ import { initializationComplete, onMessageInput, -} from '@bitbybit-dev/jscad-worker'; +} from "@bitbybit-dev/jscad-worker"; -import('@bitbybit-dev/jscad/jscad-generated').then((s) => { +import("@bitbybit-dev/jscad/jscad-generated").then((s) => { initializationComplete(s.default()); }); -addEventListener('message', ({ data }) => { +addEventListener("message", ({ data }) => { onMessageInput(data, postMessage); }); diff --git a/examples/vite/threejs/cup/src/workers/manifold.worker.ts b/examples/vite/threejs/cup/src/workers/manifold.worker.ts index 5604cfa1..09625fcb 100644 --- a/examples/vite/threejs/cup/src/workers/manifold.worker.ts +++ b/examples/vite/threejs/cup/src/workers/manifold.worker.ts @@ -1,13 +1,13 @@ import { initializationComplete, onMessageInput, -} from '@bitbybit-dev/manifold-worker'; -import Module from 'manifold-3d'; +} from "@bitbybit-dev/manifold-worker"; +import Module from "manifold-3d"; const init = async () => { const wasm = await Module({ locateFile: () => { - return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm'; + return "https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm"; }, }); wasm.setup(); @@ -16,6 +16,6 @@ const init = async () => { init(); -addEventListener('message', ({ data }) => { +addEventListener("message", ({ data }) => { onMessageInput(data, postMessage); }); diff --git a/examples/vite/threejs/cup/src/workers/occt.worker.ts b/examples/vite/threejs/cup/src/workers/occt.worker.ts index f613669d..492dfd44 100644 --- a/examples/vite/threejs/cup/src/workers/occt.worker.ts +++ b/examples/vite/threejs/cup/src/workers/occt.worker.ts @@ -1,14 +1,14 @@ -import initOpenCascade from '@bitbybit-dev/occt/bitbybit-dev-occt/cdn'; -import type { OpenCascadeInstance } from '@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.js'; +import initOpenCascade from "@bitbybit-dev/occt/bitbybit-dev-occt/cdn"; +import type { OpenCascadeInstance } from "@bitbybit-dev/occt/bitbybit-dev-occt/bitbybit-dev-occt.js"; import { initializationComplete, onMessageInput, -} from '@bitbybit-dev/occt-worker'; +} from "@bitbybit-dev/occt-worker"; initOpenCascade().then((occ: OpenCascadeInstance) => { initializationComplete(occ, undefined); }); -addEventListener('message', ({ data }) => { +addEventListener("message", ({ data }) => { onMessageInput(data, postMessage); }); diff --git a/examples/vite/threejs/hex-house-concept/src/helpers/init-kernels.ts b/examples/vite/threejs/hex-house-concept/src/helpers/init-kernels.ts index b5cd3730..aa3cc2cf 100644 --- a/examples/vite/threejs/hex-house-concept/src/helpers/init-kernels.ts +++ b/examples/vite/threejs/hex-house-concept/src/helpers/init-kernels.ts @@ -1,125 +1,127 @@ -import type { BitByBitBase } from "@bitbybit-dev/threejs"; -import type { Scene } from "three"; -import type { KernelOptions } from "../models"; -import { first, firstValueFrom, tap } from "rxjs"; +import { Scene } from "three"; +import { BitByBitBase } from "@bitbybit-dev/threejs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; +import { firstValueFrom, first, map } from "rxjs"; +import type { KernelOptions } from "../models"; export async function initKernels( - scene: Scene, - bitbybit: BitByBitBase, - options: KernelOptions -): Promise<{ message: string }> { - let occtWorkerInstance: Worker | undefined; - let jscadWorkerInstance: Worker | undefined; - let manifoldWorkerInstance: Worker | undefined; + scene: Scene, + bitbybit: BitByBitBase, + options: KernelOptions +): Promise<{ message: string; initializedKernels: string[] }> { + let occtWorkerInstance: Worker | undefined; + let jscadWorkerInstance: Worker | undefined; + let manifoldWorkerInstance: Worker | undefined; - // 1. Conditionally create worker instances - if (options.enableOCCT) { - occtWorkerInstance = new Worker( - new URL("../workers/occt.worker.ts", import.meta.url), - { name: "OCC_WORKER", type: "module" } - ); - } - if (options.enableJSCAD) { - jscadWorkerInstance = new Worker( - new URL("../workers/jscad.worker.ts", import.meta.url), - { name: "JSCAD_WORKER", type: "module" } - ); - } - if (options.enableManifold) { - manifoldWorkerInstance = new Worker( - new URL("../workers/manifold.worker.ts", import.meta.url), - { name: "MANIFOLD_WORKER", type: "module" } + // 1. Conditionally create worker instances + if (options.enableOCCT) { + occtWorkerInstance = new Worker( + new URL("../workers/occt.worker.ts", import.meta.url), + { name: "OCC_WORKER", type: "module" } + ); + } + if (options.enableJSCAD) { + jscadWorkerInstance = new Worker( + new URL("../workers/jscad.worker.ts", import.meta.url), + { name: "JSCAD_WORKER", type: "module" } + ); + } + if (options.enableManifold) { + manifoldWorkerInstance = new Worker( + new URL("../workers/manifold.worker.ts", import.meta.url), + { name: "MANIFOLD_WORKER", type: "module" } + ); + } + + // 2. Initialize Bitbybit + await bitbybit.init( + scene, + occtWorkerInstance, + jscadWorkerInstance, + manifoldWorkerInstance ); - } - // 2. Initialize Bitbybit - await bitbybit.init( - scene, - occtWorkerInstance, - jscadWorkerInstance, - manifoldWorkerInstance - ); + // 3. Collect promises for kernel initializations + const initializationPromises: Promise[] = []; + let anyKernelSelectedForInit = false; - // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; - let anyKernelSelectedForInit = false; + if (options.enableOCCT) { + anyKernelSelectedForInit = true; + if (bitbybit.occtWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.occtWorkerManager.occWorkerState$.pipe( + first((s) => s.state === OccStateEnum.initialised), + map(() => "OCCT") + ) + ) + ); + } else { + console.warn( + "OCCT enabled in options, but occtWorkerManager not found after init." + ); + } + } - if (options.enableOCCT) { - anyKernelSelectedForInit = true; - if (bitbybit.occtWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.occtWorkerManager.occWorkerState$.pipe( - first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) - ) - ).then(() => {}) // Ensure the promise resolves to void for Promise.all - ); - } else { - console.warn( - "OCCT enabled in options, but occtWorkerManager not found after init." - ); + if (options.enableJSCAD) { + anyKernelSelectedForInit = true; + if (bitbybit.jscadWorkerManager) { + initializationPromises.push( + firstValueFrom( + bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( + first((s) => s.state === JscadStateEnum.initialised), + map(() => "JSCAD") + ) + ) + ); + } else { + console.warn( + "JSCAD enabled in options, but jscadWorkerManager not found after init." + ); + } } - } - if (options.enableJSCAD) { - anyKernelSelectedForInit = true; - if (bitbybit.jscadWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( - first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "JSCAD enabled in options, but jscadWorkerManager not found after init." - ); + if (options.enableManifold) { + anyKernelSelectedForInit = true; + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { + initializationPromises.push( + firstValueFrom( + bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( + first((s) => s.state === ManifoldStateEnum.initialised), + map(() => "Manifold") + ) + ) + ); + } else { + console.warn( + "Manifold enabled in options, but manifoldWorkerManager not found after init." + ); + } } - } - if (options.enableManifold) { - anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { - initializationPromises.push( - firstValueFrom( - bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( - first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) - ) - ).then(() => {}) - ); - } else { - console.warn( - "Manifold enabled in options, but manifoldWorkerManager not found after init." - ); + // 4. Wait for selected & available kernels or handle no selection/availability + if (!anyKernelSelectedForInit) { + console.log("No kernels selected for initialization."); + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } - } - // 4. Wait for selected & available kernels or handle no selection/availability - if (!anyKernelSelectedForInit) { - console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; - } + if (initializationPromises.length === 0) { + // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) + console.log( + "Kernels were selected, but none had managers available for awaiting initialization." + ); + return { + message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], + }; + } - if (initializationPromises.length === 0) { - // Kernels were selected, but none were awaitable (e.g., managers missing for all selected) - console.log( - "Kernels were selected, but none had managers available for awaiting initialization." - ); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected kernels were not awaitable for initialization state.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; - } - - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); - return { - message: "Selected and awaitable kernels initialized successfully.", - }; } diff --git a/examples/vite/threejs/hex-house-concept/src/workers/manifold.worker.ts b/examples/vite/threejs/hex-house-concept/src/workers/manifold.worker.ts index 5604cfa1..09625fcb 100644 --- a/examples/vite/threejs/hex-house-concept/src/workers/manifold.worker.ts +++ b/examples/vite/threejs/hex-house-concept/src/workers/manifold.worker.ts @@ -1,13 +1,13 @@ import { initializationComplete, onMessageInput, -} from '@bitbybit-dev/manifold-worker'; -import Module from 'manifold-3d'; +} from "@bitbybit-dev/manifold-worker"; +import Module from "manifold-3d"; const init = async () => { const wasm = await Module({ locateFile: () => { - return 'https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm'; + return "https://cdn.jsdelivr.net/gh/bitbybit-dev/bitbybit-assets@0.21.0/wasm/manifold-3-3-2.wasm"; }, }); wasm.setup(); @@ -16,6 +16,6 @@ const init = async () => { init(); -addEventListener('message', ({ data }) => { +addEventListener("message", ({ data }) => { onMessageInput(data, postMessage); }); diff --git a/examples/vite/threejs/hex-shell/src/helpers/init-kernels.ts b/examples/vite/threejs/hex-shell/src/helpers/init-kernels.ts index 0667cc8b..aa3cc2cf 100644 --- a/examples/vite/threejs/hex-shell/src/helpers/init-kernels.ts +++ b/examples/vite/threejs/hex-shell/src/helpers/init-kernels.ts @@ -1,16 +1,16 @@ -import type { BitByBitBase } from "@bitbybit-dev/threejs"; -import type { Scene } from "three"; -import type { KernelOptions } from "../models"; -import { first, firstValueFrom, tap } from "rxjs"; +import { Scene } from "three"; +import { BitByBitBase } from "@bitbybit-dev/threejs"; import { OccStateEnum } from "@bitbybit-dev/occt-worker"; import { JscadStateEnum } from "@bitbybit-dev/jscad-worker"; import { ManifoldStateEnum } from "@bitbybit-dev/manifold-worker"; +import { firstValueFrom, first, map } from "rxjs"; +import type { KernelOptions } from "../models"; export async function initKernels( scene: Scene, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -44,7 +44,7 @@ export async function initKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { @@ -54,9 +54,9 @@ export async function initKernels( firstValueFrom( bitbybit.occtWorkerManager.occWorkerState$.pipe( first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) + map(() => "OCCT") ) - ).then(() => { }) // Ensure the promise resolves to void for Promise.all + ) ); } else { console.warn( @@ -72,9 +72,9 @@ export async function initKernels( firstValueFrom( bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) + map(() => "JSCAD") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -85,14 +85,14 @@ export async function initKernels( if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { initializationPromises.push( firstValueFrom( bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) + map(() => "Manifold") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -104,7 +104,7 @@ export async function initKernels( // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } if (initializationPromises.length === 0) { @@ -114,12 +114,14 @@ export async function initKernels( ); return { message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected and awaitable kernels initialized successfully.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; } diff --git a/examples/vite/threejs/starter-template/src/main.ts b/examples/vite/threejs/starter-template/src/main.ts index 7dc305b5..96183414 100644 --- a/examples/vite/threejs/starter-template/src/main.ts +++ b/examples/vite/threejs/starter-template/src/main.ts @@ -12,7 +12,7 @@ import { Scene, WebGLRenderer, } from "three"; -import { first, firstValueFrom, tap } from "rxjs"; +import { first, firstValueFrom, map } from "rxjs"; // Define an interface for kernel options interface KernelOptions { @@ -113,7 +113,7 @@ async function initWithKernels( scene: Scene, bitbybit: BitByBitBase, options: KernelOptions -): Promise<{ message: string }> { +): Promise<{ message: string; initializedKernels: string[] }> { let occtWorkerInstance: Worker | undefined; let jscadWorkerInstance: Worker | undefined; let manifoldWorkerInstance: Worker | undefined; @@ -147,7 +147,7 @@ async function initWithKernels( ); // 3. Collect promises for kernel initializations - const initializationPromises: Promise[] = []; + const initializationPromises: Promise[] = []; let anyKernelSelectedForInit = false; if (options.enableOCCT) { @@ -157,9 +157,9 @@ async function initWithKernels( firstValueFrom( bitbybit.occtWorkerManager.occWorkerState$.pipe( first((s) => s.state === OccStateEnum.initialised), - tap(() => console.log("OCCT Initialized")) + map(() => "OCCT") ) - ).then(() => { }) // Ensure the promise resolves to void for Promise.all + ) ); } else { console.warn( @@ -175,9 +175,9 @@ async function initWithKernels( firstValueFrom( bitbybit.jscadWorkerManager.jscadWorkerState$.pipe( first((s) => s.state === JscadStateEnum.initialised), - tap(() => console.log("JSCAD Initialized")) + map(() => "JSCAD") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -188,14 +188,14 @@ async function initWithKernels( if (options.enableManifold) { anyKernelSelectedForInit = true; - if (bitbybit.manifoldWorkerManager) { + if (bitbybit.manifoldWorkerManager && bitbybit.manifoldWorkerManager.manifoldWorkerState$) { initializationPromises.push( firstValueFrom( bitbybit.manifoldWorkerManager.manifoldWorkerState$.pipe( first((s) => s.state === ManifoldStateEnum.initialised), - tap(() => console.log("Manifold Initialized")) + map(() => "Manifold") ) - ).then(() => { }) + ) ); } else { console.warn( @@ -207,7 +207,7 @@ async function initWithKernels( // 4. Wait for selected & available kernels or handle no selection/availability if (!anyKernelSelectedForInit) { console.log("No kernels selected for initialization."); - return { message: "No kernels selected for initialization." }; + return { message: "No kernels selected for initialization.", initializedKernels: [] }; } if (initializationPromises.length === 0) { @@ -217,13 +217,15 @@ async function initWithKernels( ); return { message: "Selected kernels were not awaitable for initialization state.", + initializedKernels: [], }; } - await Promise.all(initializationPromises); - console.log("Selected and awaitable kernels initialized:", options); + const initializedKernels = await Promise.all(initializationPromises); + console.log("Kernels initialized:", initializedKernels.join(", ")); return { - message: "Selected and awaitable kernels initialized successfully.", + message: `Successfully initialized: ${initializedKernels.join(", ")}`, + initializedKernels, }; } @@ -318,26 +320,26 @@ async function createJSCADGeometry(bitbybit: BitByBitBase, color: string) { async function createTexturedOCCTCube(bitbybit: BitByBitBase) { console.log("Creating textured OCCT cube..."); - + // Create texture from URL const textureOptions = new Inputs.Draw.GenericTextureDto(); textureOptions.url = "https://cdn.polyhaven.com/asset_img/primary/worn_asphalt.png?height=760&quality=95"; textureOptions.uScale = 0.05; textureOptions.vScale = 0.05; const texture = await bitbybit.draw.createTexture(textureOptions); - + // Create material with texture const materialOptions = new Inputs.Draw.GenericPBRMaterialDto(); materialOptions.baseColorTexture = texture; materialOptions.baseColor = "#ffffff"; // White to show texture colors accurately const material = await bitbybit.draw.createPBRMaterial(materialOptions); - + // Create OCCT cube const cubeOptions = new Inputs.OCCT.CubeDto(); cubeOptions.size = 20; cubeOptions.center = [-50, 0, -50]; const cube = await bitbybit.occt.shapes.solid.createCube(cubeOptions); - + // Draw cube with material const drawOptions = new Inputs.Draw.DrawOcctShapeOptions(); drawOptions.faceMaterial = material; @@ -346,7 +348,7 @@ async function createTexturedOCCTCube(bitbybit: BitByBitBase) { entity: cube, options: drawOptions, }); - + console.log("Textured OCCT cube created and drawn."); } @@ -542,10 +544,10 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { const gridSize = 30; const spacing = 1.5; const gridOffset = [-150, 0, 0]; // Move grid away from other geometry - + const gridPoints: Inputs.Base.Point3[] = []; const gridColors: string[] = []; - + for (let x = 0; x < gridSize; x++) { for (let y = 0; y < gridSize; y++) { for (let z = 0; z < gridSize; z++) { @@ -560,12 +562,12 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { } } } - + const gridDrawOptions = new Inputs.Draw.DrawBasicGeometryOptions(); gridDrawOptions.colours = gridColors; gridDrawOptions.size = 0.4; gridDrawOptions.colorMapStrategy = Inputs.Base.colorMapStrategyEnum.lastColorRemainder; - + console.time("Draw 27,000 points"); await bitbybit.draw.drawAnyAsync({ entity: gridPoints, @@ -580,10 +582,10 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { const polylineGridSize = 30; const polylineSpacing = 1.5; const polylineGridOffset = [-150, -60, 0]; // Position below the point grid - + const gridPolylines: Inputs.Base.Polyline3[] = []; const polylineColors: string[] = []; - + // Create lines along X axis (for each Y,Z position) for (let y = 0; y < polylineGridSize; y++) { for (let z = 0; z < polylineGridSize; z++) { @@ -591,7 +593,7 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { const endX = polylineGridOffset[0] + (polylineGridSize - 1) * polylineSpacing; const posY = polylineGridOffset[1] + y * polylineSpacing; const posZ = polylineGridOffset[2] + z * polylineSpacing; - + gridPolylines.push({ points: [ [startX, posY, posZ], @@ -603,7 +605,7 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { polylineColors.push(isOrange ? "#ff6600" : "#00ffcc"); } } - + // Create lines along Y axis (for each X,Z position) for (let x = 0; x < polylineGridSize; x++) { for (let z = 0; z < polylineGridSize; z++) { @@ -611,7 +613,7 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { const startY = polylineGridOffset[1]; const endY = polylineGridOffset[1] + (polylineGridSize - 1) * polylineSpacing; const posZ = polylineGridOffset[2] + z * polylineSpacing; - + gridPolylines.push({ points: [ [posX, startY, posZ], @@ -622,7 +624,7 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { polylineColors.push(isPurple ? "#9933ff" : "#ffff00"); } } - + // Create lines along Z axis (for each X,Y position) for (let x = 0; x < polylineGridSize; x++) { for (let y = 0; y < polylineGridSize; y++) { @@ -630,7 +632,7 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { const posY = polylineGridOffset[1] + y * polylineSpacing; const startZ = polylineGridOffset[2]; const endZ = polylineGridOffset[2] + (polylineGridSize - 1) * polylineSpacing; - + gridPolylines.push({ points: [ [posX, posY, startZ], @@ -641,12 +643,12 @@ async function createDrawingExamples(bitbybit: BitByBitBase) { polylineColors.push(isPink ? "#ff0099" : "#00ff66"); } } - + const polylineGridDrawOptions = new Inputs.Draw.DrawBasicGeometryOptions(); polylineGridDrawOptions.colours = polylineColors; polylineGridDrawOptions.size = 1; polylineGridDrawOptions.colorMapStrategy = Inputs.Base.colorMapStrategyEnum.lastColorRemainder; - + console.log(`Drawing ${gridPolylines.length} polylines...`); console.time("Draw polyline grid"); await bitbybit.draw.drawAnyAsync({