Skip to content

Add isManifoldText metadata to track manifold-created 3D text#525

Open
tracygardner wants to merge 2 commits intomainfrom
claude/fix-3d-text-stl-export-CRqST
Open

Add isManifoldText metadata to track manifold-created 3D text#525
tracygardner wants to merge 2 commits intomainfrom
claude/fix-3d-text-stl-export-CRqST

Conversation

@tracygardner
Copy link
Copy Markdown
Contributor

@tracygardner tracygardner commented Apr 12, 2026

Summary

This PR adds metadata tracking for 3D text objects created using the Manifold approach, enabling proper handling of these objects in mesh operations like subtraction.

Key Changes

  • shapes.js: Set isManifoldText metadata flag on meshes created via the Manifold approach in create3DText()
  • csg.js: Updated mesh subtraction logic to recognize and properly handle manifold-created text by checking the isManifoldText metadata flag in addition to modelName
  • shapes.test.js: Added two new test cases:
    • Test verifying that isManifoldText metadata is correctly set on manifold-created text
    • Integration test validating that text can be successfully subtracted from a box without producing invalid geometry

Implementation Details

The isManifoldText flag is set on the mesh metadata after successful Manifold geometry creation. This flag is then used in the CSG subtraction pipeline to determine whether face flipping should be applied, ensuring proper mesh orientation and validity when performing boolean operations with manifold-created text objects.

https://claude.ai/code/session_01FJgv9x8zCFiEuyMEyFJokp

Summary by CodeRabbit

  • Bug Fixes

    • Recompute normals on subtraction results to ensure correct shading and geometry integrity.
    • Mark meshes produced via the Manifold 3D-text path with metadata to improve downstream handling.
  • Tests

    • Added tests verifying Manifold 3D-text metadata.
    • Added tests validating subtraction results include vertices and normal data.

…r failures

Manifold-created text meshes call flipFaces() to render correctly in Babylon.js's
left-handed coordinate system. When used as a CSG subtract tool in subtractMeshesMerge,
this flip was not undone, so Manifold saw an inverted tool solid. The resulting mesh
had incorrect winding on the text cavity walls — fine visually (backFaceCulling=false)
but written as-is to STL, causing strict slicers to reject the file.

Fix: tag manifold text with metadata.isManifoldText=true and flip its faces in
subtractMeshesMerge alongside the existing modelName flip for GLB imports.

https://claude.ai/code/session_01FJgv9x8zCFiEuyMEyFJokp
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 12, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a5e40612-c4eb-47ea-93e1-420dde3fa10e

📥 Commits

Reviewing files that changed from the base of the PR and between b12f074 and ca4c136.

📒 Files selected for processing (1)
  • api/csg.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • api/csg.js

📝 Walkthrough

Walkthrough

This PR ensures CSG subtraction outputs have normals by calling resultMesh.createNormals(true) when a non-empty result is produced, and marks Manifold-produced 3D text meshes with mesh.metadata.isManifoldText = true after vertex data is applied.

Changes

Cohort / File(s) Summary
CSG subtraction
api/csg.js
After successful CSG subtraction producing a non-empty resultMesh, recompute normals via resultMesh.createNormals(true) before applying properties and disposing inputs.
3D text (Manifold)
api/shapes.js
When generating 3D text via the Manifold path, ensure mesh.metadata exists and set mesh.metadata.isManifoldText = true after applying vertex data.
Tests
tests/shapes.test.js
Added two slow tests: one asserts metadata.isManifoldText === true for Manifold 3D text; the other creates a box and Manifold text, runs flock.subtractMeshes, and asserts result existence, non-zero vertex count, and presence of non-zero normals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

codex

Poem

🐰 I hopped through vertices late at night,
Marked manifold letters with a tiny light,
Rebuilt their normals, tidy and neat,
So CSG's result would stand on its feet.
Tests clap their paws — the mesh looks right! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: adding isManifoldText metadata to track manifold-created 3D text meshes in shapes.js.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/fix-3d-text-stl-export-CRqST

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
api/csg.js (1)

812-824: ⚠️ Potential issue | 🟡 Minor

isManifoldText flip is skipped in the direct-geometry fallback path.

At Line [820], direct tool clones bypass the new metadata-based face flip. If a tool has geometry but no material, subtraction can still run with inverted winding.

💡 Proposed fix
             } else if (meshHasGeometry) {
               // Direct mesh without children (e.g., manifold text mesh)
               const clone = cloneForCSG(mesh, `direct_tool_${meshIndex}`);
+              if (
+                (mesh.metadata?.modelName || mesh.metadata?.isManifoldText) &&
+                typeof clone.flipFaces === "function"
+              ) {
+                clone.flipFaces();
+              }
               subtractDuplicates.push(clone);
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/csg.js` around lines 812 - 824, Direct-geometry clones created in the
fallback path (when meshHasGeometry is true) skip the metadata-based face flip
logic; update the direct path that creates the clone via cloneForCSG (variable
name clone) so that if clone.metadata?.modelName ||
clone.metadata?.isManifoldText is truthy and typeof clone.flipFaces ===
"function" you call clone.flipFaces() before pushing it into subtractDuplicates,
matching the flip behavior used for unified children.
🧹 Nitpick comments (1)
tests/shapes.test.js (1)

225-260: Use returned IDs instead of hardcoded names to reduce test flakiness.

createBox/subtractMeshes can return reserved IDs. Using literals for readiness and cleanup can target the wrong mesh if names are remapped.

♻️ Suggested test hardening
-        flock.createBox("subtractBase", {
+        const baseId = flock.createBox("subtractBase", {
           color: "#ff0000",
           width: 4,
           height: 2,
           depth: 1,
           position: [0, 0, 0],
         });
-        createdIds.push("subtractBase");
+        createdIds.push(baseId);
...
-          flock.whenModelReady("subtractBase", resolve);
+          flock.whenModelReady(baseId, resolve);
...
         const resultId = await flock.subtractMeshes(
           "textSubtractResult",
-          "subtractBase",
+          baseId,
           [textId],
         );
-        createdIds.push("textSubtractResult");
+        createdIds.push(resultId);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/shapes.test.js` around lines 225 - 260, The test hardcodes mesh names
which can be remapped; instead capture and use returned IDs from
createBox/create3DText/subtractMeshes: store the createBox return in a variable
(e.g., boxId) and push that to createdIds; use that boxId and the previously
stored textId when calling whenModelReady and subtractMeshes; store the
subtractMeshes return in resultId and push that to createdIds instead of using
the literal "subtractBase" or "textSubtractResult", ensuring all readiness
checks and cleanup reference those returned variables (createBox, create3DText,
whenModelReady, subtractMeshes, createdIds, textId, resultId).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@api/csg.js`:
- Around line 812-824: Direct-geometry clones created in the fallback path (when
meshHasGeometry is true) skip the metadata-based face flip logic; update the
direct path that creates the clone via cloneForCSG (variable name clone) so that
if clone.metadata?.modelName || clone.metadata?.isManifoldText is truthy and
typeof clone.flipFaces === "function" you call clone.flipFaces() before pushing
it into subtractDuplicates, matching the flip behavior used for unified
children.

---

Nitpick comments:
In `@tests/shapes.test.js`:
- Around line 225-260: The test hardcodes mesh names which can be remapped;
instead capture and use returned IDs from createBox/create3DText/subtractMeshes:
store the createBox return in a variable (e.g., boxId) and push that to
createdIds; use that boxId and the previously stored textId when calling
whenModelReady and subtractMeshes; store the subtractMeshes return in resultId
and push that to createdIds instead of using the literal "subtractBase" or
"textSubtractResult", ensuring all readiness checks and cleanup reference those
returned variables (createBox, create3DText, whenModelReady, subtractMeshes,
createdIds, textId, resultId).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f6ccc65-8b6c-453a-93dc-ba4027b4905e

📥 Commits

Reviewing files that changed from the base of the PR and between b1a4102 and b12f074.

📒 Files selected for processing (3)
  • api/csg.js
  • api/shapes.js
  • tests/shapes.test.js

The previous commit incorrectly flipped the text tool mesh faces before
the boolean subtract, which caused Manifold CSG to treat the text as an
inverted solid (intersection instead of subtraction).

Reverted: isManifoldText condition removed from the flipFaces call in
subtractMeshesMerge. The isManifoldText metadata flag (shapes.js) is
kept for potential future use.

Added: resultMesh.createNormals(true) after outerCSG.toMesh() in both
subtractMeshesMerge and subtractMeshesIndividual, matching the pattern
already used in mergeMeshes. This ensures vertex normals are freshly
computed from the actual face geometry rather than relying on whatever
the Manifold → Babylon.js conversion produces.

https://claude.ai/code/session_01FJgv9x8zCFiEuyMEyFJokp
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Apr 12, 2026

Deploying flockxr with  Cloudflare Pages  Cloudflare Pages

Latest commit: ca4c136
Status: ✅  Deploy successful!
Preview URL: https://cf0a917b.flockxr.pages.dev
Branch Preview URL: https://claude-fix-3d-text-stl-expor.flockxr.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying flockdev with  Cloudflare Pages  Cloudflare Pages

Latest commit: ca4c136
Status: ✅  Deploy successful!
Preview URL: https://052ecbf4.flockdev.pages.dev
Branch Preview URL: https://claude-fix-3d-text-stl-expor.flockdev.pages.dev

View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants