forgecad 0.10.2 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +7 -6
  2. package/dist/assets/{AdminPage-CHY6ZN-p.js → AdminPage-CK7ObBz3.js} +1 -1
  3. package/dist/assets/{BenchmarkPage-BcRT5iGN.js → BenchmarkPage-Ds7Z2doN.js} +1 -1
  4. package/dist/assets/{BlogPage-BssBbnb-.js → BlogPage-DlPbpt6A.js} +1 -1
  5. package/dist/assets/{DocsPage-DsvdiRNK.js → DocsPage-vZb3b3Y0.js} +9 -14
  6. package/dist/assets/{EditorApp-BpjZgzk0.css → EditorApp-C5f24ZN9.css} +8 -0
  7. package/dist/assets/{EditorApp-Bfd3jbtC.js → EditorApp-HLoKfe15.js} +141 -12
  8. package/dist/assets/{EmbedViewer-D5t8WamV.js → EmbedViewer--KnqBKrJ.js} +2 -2
  9. package/dist/assets/{LandingPageProofDriven-DbN7o-Be.js → LandingPageProofDriven-C_LssmnA.js} +1 -1
  10. package/dist/assets/{LegalPage-DNGrrY0p.js → LegalPage-DGsyo4n1.js} +1 -1
  11. package/dist/assets/{PricingPage-Nczr3pRz.js → PricingPage-BOE27B-R.js} +1 -1
  12. package/dist/assets/{SettingsPage-DZlyu4d4.js → SettingsPage-f47cnk39.js} +1 -1
  13. package/dist/assets/{app-C9ct2hRD.js → app-D6ccu2Xx.js} +6854 -7373
  14. package/dist/assets/{backendInit-ymjonyQp.js → backendInit-DbTkQN9J.js} +2557 -809
  15. package/dist/assets/cli/{render-B_0lQwKU.js → render-BsngirjC.js} +114 -9
  16. package/dist/assets/{constructionHistoryWorker-CZ42Dksy.js → constructionHistoryWorker-PCwXrTDB.js} +175 -36
  17. package/dist/assets/{evalWorker-C2pm8LHP.js → evalWorker-CS63PfZu.js} +1125 -447
  18. package/dist/assets/{forgecad_geometry-BlMtqluF.js → forgecad_geometry-CZ_IfuvA.js} +1 -9
  19. package/dist/assets/{forgecad_geometry_bg-BllP_WiL.wasm → forgecad_geometry_bg-C3rQHfwg.wasm} +0 -0
  20. package/dist/assets/{inspectWorker-D5T5VbfK.js → inspectWorker-Y4cOzNyA.js} +4345 -373
  21. package/dist/assets/{jointPose-4r8ed8_5.js → jointPose-AMvCywzS.js} +1 -1
  22. package/dist/assets/{manifold-C4r6B-XY.js → manifold-CBry38ly.js} +2 -2
  23. package/dist/assets/{manifold-5PP1eGLN.js → manifold-Crd_F2qx.js} +1 -1
  24. package/dist/assets/{manifold-DjBkyIc8.js → manifold-k2kRcc85.js} +1 -1
  25. package/dist/assets/{reportWorker-CwenM7wB.js → reportWorker-CWvn0CEv.js} +1095 -400
  26. package/dist/cli/render.html +1 -1
  27. package/dist/docs/index.html +2 -2
  28. package/dist/docs-raw/AI/usage.md +2 -4
  29. package/dist/docs-raw/CLI.md +9 -7
  30. package/dist/docs-raw/README.md +1 -1
  31. package/dist/docs-raw/component-model.md +1 -1
  32. package/dist/docs-raw/generated/assembly.md +1 -1
  33. package/dist/docs-raw/generated/concepts.md +5 -3
  34. package/dist/docs-raw/generated/core.md +70 -1
  35. package/dist/docs-raw/generated/curves.md +8 -1
  36. package/dist/docs-raw/generated/output.md +0 -64
  37. package/dist/docs-raw/generated/runtime-names.md +6 -6
  38. package/dist/docs-raw/generated/viewport.md +3 -12
  39. package/dist/docs-raw/guides/inspection-bundles.md +1 -1
  40. package/dist/docs-raw/simulation-workflow.md +58 -0
  41. package/dist/docs-raw/skills/forgecad-blockout-model.md +1 -1
  42. package/dist/docs-raw/skills/forgecad-image-replicator.md +2 -2
  43. package/dist/docs-raw/skills/forgecad-mujoco-verify.md +78 -0
  44. package/dist/docs-raw/skills/forgecad-spec-by-walking-through-it.md +145 -0
  45. package/dist/docs-raw/skills/forgecad-visual-spec.md +1 -1
  46. package/dist/docs-raw/skills/forgecad.md +24 -24
  47. package/dist/docs-raw/skills/index.md +2 -3
  48. package/dist/index.html +1 -1
  49. package/dist/sitemap.xml +15 -15
  50. package/dist-cli/{check-compiler-SP7FAL7R.js → check-compiler-HPF2T2FS.js} +1 -1
  51. package/dist-cli/{check-query-propagation-BRLSHP22.js → check-query-propagation-HYSLTXAB.js} +1 -1
  52. package/dist-cli/{chunk-RQQ42YCP.js → chunk-WLUKAW3H.js} +1025 -158
  53. package/dist-cli/forgecad.js +2621 -232
  54. package/dist-cli/{forgecad_geometry-7TVSNVUB.js → forgecad_geometry-2IMYCUWW.js} +0 -8
  55. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  56. package/dist-skill/CONTEXT.md +85 -73
  57. package/dist-skill/SKILL.md +1 -1
  58. package/dist-skill/docs/CLI.md +9 -7
  59. package/dist-skill/docs/generated/assembly.md +1 -1
  60. package/dist-skill/docs/generated/core.md +70 -1
  61. package/dist-skill/docs/generated/curves.md +8 -1
  62. package/dist-skill/docs/generated/output.md +0 -64
  63. package/dist-skill/docs/generated/runtime-names.md +6 -6
  64. package/dist-skill/docs/generated/viewport.md +3 -12
  65. package/dist-skill/docs/guides/inspection-bundles.md +1 -1
  66. package/dist-skill/library/README.md +2 -3
  67. package/dist-skill/library/forgecad-blockout-model/SKILL.md +1 -1
  68. package/dist-skill/library/forgecad-image-replicator/SKILL.md +2 -2
  69. package/dist-skill/library/forgecad-mujoco-verify/SKILL.md +66 -0
  70. package/dist-skill/library/forgecad-mujoco-verify/scripts/mujoco_verify.py +385 -0
  71. package/dist-skill/library/forgecad-spec-by-walking-through-it/SKILL.md +132 -0
  72. package/dist-skill/library/forgecad-visual-spec/SKILL.md +1 -1
  73. package/dist-skill/website/skills/forgecad-blockout-model.md +1 -1
  74. package/dist-skill/website/skills/forgecad-image-replicator.md +2 -2
  75. package/dist-skill/website/skills/forgecad-mujoco-verify.md +78 -0
  76. package/dist-skill/website/skills/forgecad-spec-by-walking-through-it.md +145 -0
  77. package/dist-skill/website/skills/forgecad-visual-spec.md +1 -1
  78. package/dist-skill/website/skills/forgecad.md +24 -24
  79. package/dist-skill/website/skills/index.md +2 -3
  80. package/examples/analysis/clearance-fit.forge.js +31 -0
  81. package/examples/analysis/lever-arm-actuator.forge.js +43 -0
  82. package/examples/analysis/tipping-tripod.forge.js +35 -0
  83. package/examples/products/sportscar.forge.js +77 -0
  84. package/package.json +1 -3
  85. package/dist/docs-raw/skills/forgecad-high-level-spec.md +0 -101
  86. package/dist/docs-raw/skills/forgecad-lld.md +0 -41
  87. package/dist/docs-raw/skills/forgecad-prepare-prompt.md +0 -63
  88. package/dist-skill/library/forgecad-high-level-spec/SKILL.md +0 -94
  89. package/dist-skill/library/forgecad-lld/SKILL.md +0 -34
  90. package/dist-skill/library/forgecad-prepare-prompt/SKILL.md +0 -50
  91. package/dist-skill/website/skills/forgecad-high-level-spec.md +0 -101
  92. package/dist-skill/website/skills/forgecad-lld.md +0 -41
  93. package/dist-skill/website/skills/forgecad-prepare-prompt.md +0 -63
  94. /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/default-profiles.md +0 -0
  95. /package/dist-skill/library/{forgecad-prepare-prompt → forgecad-spec-by-walking-through-it}/references/master-prompt.md +0 -0
@@ -1,8 +1,8 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
3
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
- import { D as DoubleSide, a as Scene, bx as BoxGeometry, c2 as MeshStandardMaterial, a5 as BackSide, b8 as PointLight, M as Mesh, ab as MeshBasicMaterial, cJ as localAabbPlaneRelation, i as Vector2, cK as ShapeUtils, cL as analyzePhysicalConnectivity, h as Vector3, a0 as Matrix4, cM as Frustum, J as Box3, a1 as MathUtils, cN as meshContactDataFor, cO as AabbSpatialIndex, cP as detectPhysicalContact, cQ as resolveThicknessInspectionOptions, R as Raycaster, cR as thicknessColor, cS as thicknessClass, b0 as BufferAttribute, cT as roughnessClassForAngle, cU as resolveRoughnessInspectionOptions, cV as roughnessColorForAngle, cW as roughnessScoreForAngle, cI as initBackendForEvaluation, f as Color, bb as COMPARISON_COLORS, az as resolveForgeRenderStyle, bG as getRenderStylePreset, ay as setParamOverrides, bm as runScript, cs as scanProxyGridForBounds, cX as Group, bf as shapeToGeometry, bn as MeshPhysicalMaterial, bE as AdditiveBlending, bJ as scanMaterialShellColor, cY as createScanProxyGeometry, aP as LineBasicMaterial, bI as NormalBlending, bo as LineSegments, P as PerspectiveCamera, cp as DEFAULT_VIEW_CONFIG, bt as worldAuthorPlaneToLocal, cZ as resolveSectionHatchMetrics, cA as buildGeometryComparisonPointCloud, cy as triangleSoupFromMeshes, O as OrthographicCamera, l as ShaderMaterial, bP as ZEBRA_STRIPE_FRAGMENT_SHADER, bQ as ZEBRA_STRIPE_VERTEX_SHADER, bK as ZEBRA_STRIPE_SOFTNESS, bL as ZEBRA_STRIPE_SCALE, bM as ZEBRA_LIGHT_COLOR, bN as ZEBRA_DARK_COLOR, bO as ZEBRA_ACCENT_COLOR, bF as geometryWithVisibleVertexColors, c_ as intersectWithPlane, c$ as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, d as SRGBColorSpace, d0 as parseCameraCliSpec, d1 as PMREMGenerator, b1 as CanvasTexture, b2 as Object3D, b3 as FogExp2, b4 as Fog, b5 as AmbientLight, b9 as DirectionalLight, b6 as HemisphereLight, aJ as findJointAnimationClip, q as Plane, bT as SURFACE_FIELD_FRAGMENT_SHADER, bU as SURFACE_FIELD_VERTEX_SHADER, bS as scanMaterialLayerStyles, bR as SCAN_PROXY_LAYER_STYLES, aK as resolveJointAnimation, aL as resolveJointViewValues, aO as BufferGeometry, bZ as ROUGHNESS_COLORS, d2 as PointsMaterial, d3 as Points, ba as heatPointsForSide, d4 as analyzeCollisionIntersections, d5 as serializeCollisionFinding, d6 as summarizeThicknessSamples, bY as THICKNESS_GRADIENT_COLORS, d7 as THICKNESS_COLORS, b7 as SpotLight, c6 as CylinderGeometry, d8 as TorusGeometry, bW as CatmullRomCurve3, bX as TubeGeometry, cE as resolveScalarSceneSampleBudget, bg as buildComparisonHeatPatchGeometry, bh as EdgesGeometry, d9 as SphereGeometry, da as ConeGeometry, bc as comparisonHeatDepthTest, bd as comparisonHeatEdgeOpacity, be as comparisonHeatPatchOpacity, cG as comparisonCandidateContextOpacity, db as DEFAULT_COMPARISON_CANDIDATE_OPACITY } from "../backendInit-ymjonyQp.js";
5
- import { m as mergeViewportRenderSceneStates, v as validateJointOverrides, b as buildBaseJointValues, p as parseRenderSceneCliSpec, g as getSceneObjectTreePath } from "../jointPose-4r8ed8_5.js";
4
+ import { D as DoubleSide, a as Scene, bw as BoxGeometry, c1 as MeshStandardMaterial, a5 as BackSide, b2 as PointLight, M as Mesh, af as MeshBasicMaterial, cF as localAabbPlaneRelation, i as Vector2, cG as ShapeUtils, cH as analyzePhysicalConnectivity, h as Vector3, a0 as Matrix4, cI as Frustum, J as Box3, a1 as MathUtils, cJ as meshContactDataFor, cK as AabbSpatialIndex, cL as detectPhysicalContact, cM as resolveThicknessInspectionOptions, R as Raycaster, cN as thicknessColor, cO as thicknessClass, a6 as BufferAttribute, cE as initBackendForEvaluation, f as Color, b5 as COMPARISON_COLORS, aE as resolveForgeRenderStyle, bG as getRenderStylePreset, aD as setParamOverrides, bk as runScript, cp as scanProxyGridForBounds, cP as Group, b9 as shapeToGeometry, bl as MeshPhysicalMaterial, bD as AdditiveBlending, bJ as scanMaterialShellColor, cQ as createScanProxyGeometry, aU as LineBasicMaterial, bI as NormalBlending, bm as LineSegments, P as PerspectiveCamera, cm as DEFAULT_VIEW_CONFIG, bs as worldAuthorPlaneToLocal, cR as resolveSectionHatchMetrics, cv as buildGeometryComparisonPointCloud, ct as triangleSoupFromMeshes, O as OrthographicCamera, l as ShaderMaterial, bP as ZEBRA_STRIPE_FRAGMENT_SHADER, bQ as ZEBRA_STRIPE_VERTEX_SHADER, bK as ZEBRA_STRIPE_SOFTNESS, bL as ZEBRA_STRIPE_SCALE, bM as ZEBRA_LIGHT_COLOR, bN as ZEBRA_DARK_COLOR, bO as ZEBRA_ACCENT_COLOR, bE as geometryWithVisibleVertexColors, cS as intersectWithPlane, cT as setActiveBackend, W as WebGLRenderer, A as ACESFilmicToneMapping, d as SRGBColorSpace, cU as parseCameraCliSpec, cV as PMREMGenerator, aX as CanvasTexture, aY as Object3D, aZ as FogExp2, a_ as Fog, a$ as AmbientLight, b3 as DirectionalLight, b0 as HemisphereLight, aO as findJointAnimationClip, q as Plane, bT as SURFACE_FIELD_FRAGMENT_SHADER, bU as SURFACE_FIELD_VERTEX_SHADER, bS as scanMaterialLayerStyles, bR as SCAN_PROXY_LAYER_STYLES, aP as resolveJointAnimation, aQ as resolveJointViewValues, aT as BufferGeometry, cW as DEFAULT_ROUGHNESS_COLOR_SCALE, cX as PointsMaterial, cY as Points, b4 as heatPointsForSide, cZ as analyzeCollisionIntersections, c_ as serializeCollisionFinding, c$ as summarizeThicknessSamples, d0 as THICKNESS_GRADIENT_COLORS, d1 as THICKNESS_COLORS, d2 as DEFAULT_THICKNESS_COLOR_SCALE, b1 as SpotLight, c5 as CylinderGeometry, d3 as TorusGeometry, bW as CatmullRomCurve3, bX as TubeGeometry, cz as resolveScalarSceneSampleBudget, d4 as sampleColorScale, ba as buildComparisonHeatPatchGeometry, bb as EdgesGeometry, d5 as SphereGeometry, d6 as ConeGeometry, b6 as comparisonHeatDepthTest, b7 as comparisonHeatEdgeOpacity, b8 as comparisonHeatPatchOpacity, cC as comparisonCandidateContextOpacity, d7 as DEFAULT_COMPARISON_CANDIDATE_OPACITY } from "../backendInit-DbTkQN9J.js";
5
+ import { m as mergeViewportRenderSceneStates, v as validateJointOverrides, b as buildBaseJointValues, p as parseRenderSceneCliSpec, g as getSceneObjectTreePath } from "../jointPose-AMvCywzS.js";
6
6
  const CAD_MATERIAL_PROPS = {
7
7
  color: 6003669,
8
8
  metalness: 0.05,
@@ -1464,6 +1464,79 @@ function analyzeThicknessGeometry(sourceGeometry, rawOptions = {}, context = {})
1464
1464
  warnings
1465
1465
  };
1466
1466
  }
1467
+ const DEFAULT_ROUGHNESS_INSPECTION_OPTIONS = {
1468
+ smoothAngleDeg: 5,
1469
+ sharpAngleDeg: 30,
1470
+ harshAngleDeg: 90,
1471
+ maxSamplesPerObject: 5e3
1472
+ };
1473
+ const ROUGHNESS_COLORS = {
1474
+ smooth: [62, 72, 84],
1475
+ moderate: [255, 214, 0],
1476
+ sharp: [255, 124, 34],
1477
+ harsh: [255, 42, 96]
1478
+ };
1479
+ function resolveRoughnessInspectionOptions(raw = {}) {
1480
+ const options = {
1481
+ smoothAngleDeg: raw.smoothAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.smoothAngleDeg,
1482
+ sharpAngleDeg: raw.sharpAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.sharpAngleDeg,
1483
+ harshAngleDeg: raw.harshAngleDeg ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.harshAngleDeg,
1484
+ maxSamplesPerObject: raw.maxSamplesPerObject ?? DEFAULT_ROUGHNESS_INSPECTION_OPTIONS.maxSamplesPerObject
1485
+ };
1486
+ if (!Number.isFinite(options.smoothAngleDeg) || options.smoothAngleDeg < 0) {
1487
+ throw new Error(`smoothAngleDeg must be a finite non-negative angle (got ${options.smoothAngleDeg}).`);
1488
+ }
1489
+ if (!Number.isFinite(options.sharpAngleDeg) || options.sharpAngleDeg <= options.smoothAngleDeg) {
1490
+ throw new Error(`sharpAngleDeg must be greater than smoothAngleDeg (got ${options.sharpAngleDeg}).`);
1491
+ }
1492
+ if (!Number.isFinite(options.harshAngleDeg) || options.harshAngleDeg <= options.sharpAngleDeg || options.harshAngleDeg > 180) {
1493
+ throw new Error(`harshAngleDeg must be greater than sharpAngleDeg and <= 180 (got ${options.harshAngleDeg}).`);
1494
+ }
1495
+ if (!Number.isFinite(options.maxSamplesPerObject) || options.maxSamplesPerObject <= 0) {
1496
+ throw new Error(`maxSamplesPerObject must be a positive finite number (got ${options.maxSamplesPerObject}).`);
1497
+ }
1498
+ return {
1499
+ ...options,
1500
+ maxSamplesPerObject: Math.max(1, Math.floor(options.maxSamplesPerObject))
1501
+ };
1502
+ }
1503
+ function roughnessClassForAngle(angleDeg, options) {
1504
+ if (angleDeg >= options.harshAngleDeg) return "harsh";
1505
+ if (angleDeg >= options.sharpAngleDeg) return "sharp";
1506
+ if (angleDeg >= options.smoothAngleDeg) return "moderate";
1507
+ return "smooth";
1508
+ }
1509
+ function roughnessScoreForAngle(angleDeg, options) {
1510
+ if (angleDeg < options.sharpAngleDeg) return 0;
1511
+ if (angleDeg < options.harshAngleDeg) {
1512
+ return MathUtils.lerp(0.48, 0.82, (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg));
1513
+ }
1514
+ return 1;
1515
+ }
1516
+ function roughnessColorForAngle(angleDeg, options) {
1517
+ const cls = roughnessClassForAngle(angleDeg, options);
1518
+ if (cls === "smooth" || cls === "harsh") return ROUGHNESS_COLORS[cls];
1519
+ if (cls === "moderate") {
1520
+ return lerpRgb(
1521
+ ROUGHNESS_COLORS.moderate,
1522
+ ROUGHNESS_COLORS.sharp,
1523
+ (angleDeg - options.smoothAngleDeg) / (options.sharpAngleDeg - options.smoothAngleDeg)
1524
+ );
1525
+ }
1526
+ return lerpRgb(
1527
+ ROUGHNESS_COLORS.sharp,
1528
+ ROUGHNESS_COLORS.harsh,
1529
+ (angleDeg - options.sharpAngleDeg) / (options.harshAngleDeg - options.sharpAngleDeg)
1530
+ );
1531
+ }
1532
+ function lerpRgb(a, b, t) {
1533
+ const clamped = MathUtils.clamp(t, 0, 1);
1534
+ return [
1535
+ Math.round(MathUtils.lerp(a[0], b[0], clamped)),
1536
+ Math.round(MathUtils.lerp(a[1], b[1], clamped)),
1537
+ Math.round(MathUtils.lerp(a[2], b[2], clamped))
1538
+ ];
1539
+ }
1467
1540
  function emptyRoughnessSummary() {
1468
1541
  return {
1469
1542
  triangleCount: 0,
@@ -3602,7 +3675,7 @@ function bboxFromGeometry(geometry) {
3602
3675
  max: bbox ? [bbox.max.x, bbox.max.y, bbox.max.z] : [0, 0, 0]
3603
3676
  };
3604
3677
  }
3605
- function pointBuffersFromSamples(samples, offset = 0.025) {
3678
+ function pointBuffersFromSamples(samples, colorScale, offset = 0.025) {
3606
3679
  const positions = new Float32Array(samples.length * 3);
3607
3680
  const colors = new Float32Array(samples.length * 3);
3608
3681
  samples.forEach((sample, index) => {
@@ -3610,9 +3683,16 @@ function pointBuffersFromSamples(samples, offset = 0.025) {
3610
3683
  positions[base] = sample.position[0] + sample.normal[0] * offset;
3611
3684
  positions[base + 1] = sample.position[1] + sample.normal[1] * offset;
3612
3685
  positions[base + 2] = sample.position[2] + sample.normal[2] * offset;
3613
- colors[base] = sample.color[0] / 255;
3614
- colors[base + 1] = sample.color[1] / 255;
3615
- colors[base + 2] = sample.color[2] / 255;
3686
+ if (colorScale && sample.value != null && Number.isFinite(sample.value)) {
3687
+ const [r, g, b] = sampleColorScale(colorScale, sample.value);
3688
+ colors[base] = r / 255;
3689
+ colors[base + 1] = g / 255;
3690
+ colors[base + 2] = b / 255;
3691
+ } else {
3692
+ colors[base] = sample.color[0] / 255;
3693
+ colors[base + 1] = sample.color[1] / 255;
3694
+ colors[base + 2] = sample.color[2] / 255;
3695
+ }
3616
3696
  });
3617
3697
  return { positions, colors };
3618
3698
  }
@@ -3680,6 +3760,11 @@ function getSessionThicknessInspection(session, rawOptions) {
3680
3760
  const byId = new Map(session.objects.map((obj) => [obj.id, obj]));
3681
3761
  const warnings = [];
3682
3762
  maybePushSceneSampleBudgetWarning(warnings, "Thickness", sampleBudget);
3763
+ const thicknessColorScale = {
3764
+ colormap: DEFAULT_THICKNESS_COLOR_SCALE.colormap,
3765
+ domainMin: options.colorMinThickness,
3766
+ domainMax: options.colorMaxThickness
3767
+ };
3683
3768
  const objects = [];
3684
3769
  const cloudObjects = [];
3685
3770
  const points = [];
@@ -3727,7 +3812,7 @@ function getSessionThicknessInspection(session, rawOptions) {
3727
3812
  ...sample
3728
3813
  });
3729
3814
  });
3730
- overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples) });
3815
+ overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples, thicknessColorScale) });
3731
3816
  analysis.geometry.dispose();
3732
3817
  });
3733
3818
  const state = {
@@ -3750,7 +3835,10 @@ function getSessionThicknessInspection(session, rawOptions) {
3750
3835
  objects,
3751
3836
  warnings,
3752
3837
  style: {
3838
+ // gradientColors is the legacy 'thickness-classic' rainbow, kept for
3839
+ // back-compat. colorScale is the truth the viewport/CLI now render with.
3753
3840
  gradientColors: THICKNESS_GRADIENT_COLORS.map((color) => [...color]),
3841
+ colorScale: thicknessColorScale,
3754
3842
  colorMinThickness: options.colorMinThickness,
3755
3843
  colorMaxThickness: options.colorMaxThickness,
3756
3844
  unknownColor: THICKNESS_COLORS.unknown
@@ -3783,8 +3871,22 @@ function getSessionRoughnessInspection(session, rawOptions) {
3783
3871
  const cloudObjects = [];
3784
3872
  const points = [];
3785
3873
  const overlays = [];
3786
- session.renderables.forEach((renderable, index) => {
3874
+ let roughnessMin = Infinity;
3875
+ let roughnessMax = -Infinity;
3876
+ const analysesByIndex = [];
3877
+ session.renderables.forEach((renderable) => {
3787
3878
  const analysis = analyzeRoughnessGeometry(renderable.solid.geometry, options);
3879
+ analysesByIndex.push(analysis);
3880
+ for (const sample of analysis.pointSamples) {
3881
+ if (sample.value != null && Number.isFinite(sample.value)) {
3882
+ if (sample.value < roughnessMin) roughnessMin = sample.value;
3883
+ if (sample.value > roughnessMax) roughnessMax = sample.value;
3884
+ }
3885
+ }
3886
+ });
3887
+ const roughnessColorScale = Number.isFinite(roughnessMin) && roughnessMax > roughnessMin ? { colormap: DEFAULT_ROUGHNESS_COLOR_SCALE.colormap, domainMin: roughnessMin, domainMax: roughnessMax } : DEFAULT_ROUGHNESS_COLOR_SCALE;
3888
+ session.renderables.forEach((renderable, index) => {
3889
+ const analysis = analysesByIndex[index];
3788
3890
  const bbox = bboxFromGeometry(analysis.geometry);
3789
3891
  const sourceObject = byId.get(renderable.id);
3790
3892
  if (analysis.warnings.length > 0) {
@@ -3819,7 +3921,7 @@ function getSessionRoughnessInspection(session, rawOptions) {
3819
3921
  ...sample
3820
3922
  });
3821
3923
  });
3822
- overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples) });
3924
+ overlays.push({ renderable, ...pointBuffersFromSamples(analysis.pointSamples, roughnessColorScale) });
3823
3925
  analysis.geometry.dispose();
3824
3926
  });
3825
3927
  const state = {
@@ -3842,6 +3944,9 @@ function getSessionRoughnessInspection(session, rawOptions) {
3842
3944
  objects,
3843
3945
  warnings,
3844
3946
  style: {
3947
+ // Legacy class colors kept for back-compat; colorScale is the truth the
3948
+ // viewport/CLI now render with (continuous viridis over the data domain).
3949
+ colorScale: roughnessColorScale,
3845
3950
  smoothColor: ROUGHNESS_COLORS.smooth,
3846
3951
  moderateColor: ROUGHNESS_COLORS.moderate,
3847
3952
  sharpColor: ROUGHNESS_COLORS.sharp,
@@ -11427,6 +11427,65 @@ function basisFuns(span, u2, degree, knots) {
11427
11427
  }
11428
11428
  return N;
11429
11429
  }
11430
+ function basisFunsDeriv(span, u2, degree, knots, nDeriv) {
11431
+ const ndu = Array.from({ length: degree + 1 }, () => new Array(degree + 1).fill(0));
11432
+ const a2 = Array.from({ length: 2 }, () => new Array(degree + 1).fill(0));
11433
+ const left = new Array(degree + 1);
11434
+ const right = new Array(degree + 1);
11435
+ ndu[0][0] = 1;
11436
+ for (let j = 1; j <= degree; j++) {
11437
+ left[j] = u2 - knots[span + 1 - j];
11438
+ right[j] = knots[span + j] - u2;
11439
+ let saved = 0;
11440
+ for (let r2 = 0; r2 < j; r2++) {
11441
+ ndu[j][r2] = right[r2 + 1] + left[j - r2];
11442
+ const temp = ndu[j][r2] === 0 ? 0 : ndu[r2][j - 1] / ndu[j][r2];
11443
+ ndu[r2][j] = saved + right[r2 + 1] * temp;
11444
+ saved = left[j - r2] * temp;
11445
+ }
11446
+ ndu[j][j] = saved;
11447
+ }
11448
+ const ders = Array.from({ length: nDeriv + 1 }, () => new Array(degree + 1).fill(0));
11449
+ for (let j = 0; j <= degree; j++) {
11450
+ ders[0][j] = ndu[j][degree];
11451
+ }
11452
+ for (let r2 = 0; r2 <= degree; r2++) {
11453
+ let s1 = 0;
11454
+ let s2 = 1;
11455
+ a2[0][0] = 1;
11456
+ for (let k2 = 1; k2 <= nDeriv; k2++) {
11457
+ let d2 = 0;
11458
+ const rk = r2 - k2;
11459
+ const pk = degree - k2;
11460
+ if (r2 >= k2) {
11461
+ a2[s2][0] = ndu[pk + 1][rk] === 0 ? 0 : a2[s1][0] / ndu[pk + 1][rk];
11462
+ d2 = a2[s2][0] * ndu[rk][pk];
11463
+ }
11464
+ const j1 = rk >= -1 ? 1 : -rk;
11465
+ const j2 = r2 - 1 <= pk ? k2 - 1 : degree - r2;
11466
+ for (let j = j1; j <= j2; j++) {
11467
+ a2[s2][j] = ndu[pk + 1][rk + j] === 0 ? 0 : (a2[s1][j] - a2[s1][j - 1]) / ndu[pk + 1][rk + j];
11468
+ d2 += a2[s2][j] * ndu[rk + j][pk];
11469
+ }
11470
+ if (r2 <= pk) {
11471
+ a2[s2][k2] = ndu[pk + 1][r2] === 0 ? 0 : -a2[s1][k2 - 1] / ndu[pk + 1][r2];
11472
+ d2 += a2[s2][k2] * ndu[r2][pk];
11473
+ }
11474
+ ders[k2][r2] = d2;
11475
+ const tmp = s1;
11476
+ s1 = s2;
11477
+ s2 = tmp;
11478
+ }
11479
+ }
11480
+ let r = degree;
11481
+ for (let k2 = 1; k2 <= nDeriv; k2++) {
11482
+ for (let j = 0; j <= degree; j++) {
11483
+ ders[k2][j] *= r;
11484
+ }
11485
+ r *= degree - k2;
11486
+ }
11487
+ return ders;
11488
+ }
11430
11489
  function deBoor3D(controlPoints, weights, knots, degree, u2) {
11431
11490
  const n = controlPoints.length;
11432
11491
  const span = findSpan(n, degree, u2, knots);
@@ -16863,7 +16922,7 @@ async function initTruckGeometryWasm() {
16863
16922
  if (_initPromise$1) return _initPromise$1;
16864
16923
  _initPromise$1 = (async () => {
16865
16924
  try {
16866
- const geometryModule = await import("./forgecad_geometry-BlMtqluF.js");
16925
+ const geometryModule = await import("./forgecad_geometry-CZ_IfuvA.js");
16867
16926
  const isNode = isNodeRuntime();
16868
16927
  if (isNode) {
16869
16928
  const { readFileSync, existsSync } = await import("./__vite-browser-external-Dhvy_jtL.js");
@@ -17328,23 +17387,62 @@ class NurbsSurface {
17328
17387
  return [wx / wSum, wy / wSum, wz / wSum];
17329
17388
  }
17330
17389
  /**
17331
- * Evaluate the surface normal at (u, v) via cross product of partial derivatives.
17390
+ * Evaluate the surface unit normal at (u, v) from analytic first derivatives.
17391
+ *
17392
+ * Uses Algorithm A2.3 basis-function derivatives with the rational quotient
17393
+ * rule, so the normal is exact (no finite-difference epsilon, no error near
17394
+ * the boundary). Constant chain-rule factors from the parameter remap scale Su
17395
+ * and Sv positively and cancel under normalization, so they are omitted.
17332
17396
  */
17333
17397
  normalAt(u2, v) {
17334
- const eps = 1e-5;
17335
- const u0 = Math.max(0, u2 - eps), u1 = Math.min(1, u2 + eps);
17336
- const v0 = Math.max(0, v - eps), v1 = Math.min(1, v + eps);
17337
- const pu = this.pointAt(u1, v), pmu = this.pointAt(u0, v);
17338
- const pv = this.pointAt(u2, v1), pmv = this.pointAt(u2, v0);
17339
- const du = [pu[0] - pmu[0], pu[1] - pmu[1], pu[2] - pmu[2]];
17340
- const dv = [pv[0] - pmv[0], pv[1] - pmv[1], pv[2] - pmv[2]];
17341
- const nx = du[1] * dv[2] - du[2] * dv[1];
17342
- const ny = du[2] * dv[0] - du[0] * dv[2];
17343
- const nz = du[0] * dv[1] - du[1] * dv[0];
17398
+ const { Su, Sv } = this.derivativesAt(u2, v);
17399
+ const nx = Su[1] * Sv[2] - Su[2] * Sv[1];
17400
+ const ny = Su[2] * Sv[0] - Su[0] * Sv[2];
17401
+ const nz = Su[0] * Sv[1] - Su[1] * Sv[0];
17344
17402
  const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
17345
17403
  if (len < 1e-12) return [0, 0, 1];
17346
17404
  return [nx / len, ny / len, nz / len];
17347
17405
  }
17406
+ /** Analytic first partial derivatives S_u, S_v (rational quotient rule). */
17407
+ derivativesAt(u2, v) {
17408
+ const uu = this.remapU(Math.max(0, Math.min(1, u2)));
17409
+ const vv = this.remapV(Math.max(0, Math.min(1, v)));
17410
+ const spanU = findSpan(this.nU, this.degreeU, uu, this.knotsU);
17411
+ const spanV = findSpan(this.nV, this.degreeV, vv, this.knotsV);
17412
+ const dU = basisFunsDeriv(spanU, uu, this.degreeU, this.knotsU, 1);
17413
+ const dV = basisFunsDeriv(spanV, vv, this.degreeV, this.knotsV, 1);
17414
+ const A = [0, 0, 0];
17415
+ const Au = [0, 0, 0];
17416
+ const Av = [0, 0, 0];
17417
+ let w2 = 0;
17418
+ let wu = 0;
17419
+ let wv = 0;
17420
+ for (let i = 0; i <= this.degreeU; i++) {
17421
+ const rowIdx = spanU - this.degreeU + i;
17422
+ for (let j = 0; j <= this.degreeV; j++) {
17423
+ const colIdx = spanV - this.degreeV + j;
17424
+ const weight = this.weightsGrid[rowIdx][colIdx];
17425
+ const pt = this.controlGrid[rowIdx][colIdx];
17426
+ const n00 = dU[0][i] * dV[0][j] * weight;
17427
+ const n10 = dU[1][i] * dV[0][j] * weight;
17428
+ const n01 = dU[0][i] * dV[1][j] * weight;
17429
+ w2 += n00;
17430
+ wu += n10;
17431
+ wv += n01;
17432
+ for (let c2 = 0; c2 < 3; c2++) {
17433
+ A[c2] += n00 * pt[c2];
17434
+ Au[c2] += n10 * pt[c2];
17435
+ Av[c2] += n01 * pt[c2];
17436
+ }
17437
+ }
17438
+ }
17439
+ if (w2 === 0) return { S: [0, 0, 0], Su: [0, 0, 0], Sv: [0, 0, 0] };
17440
+ const invW = 1 / w2;
17441
+ const S = [A[0] * invW, A[1] * invW, A[2] * invW];
17442
+ const Su = [(Au[0] - wu * S[0]) * invW, (Au[1] - wu * S[1]) * invW, (Au[2] - wu * S[2]) * invW];
17443
+ const Sv = [(Av[0] - wv * S[0]) * invW, (Av[1] - wv * S[1]) * invW, (Av[2] - wv * S[2]) * invW];
17444
+ return { S, Su, Sv };
17445
+ }
17348
17446
  /**
17349
17447
  * Tessellate the surface into a triangle mesh.
17350
17448
  * Returns positions, normals, and triangle indices.
@@ -19656,6 +19754,20 @@ function fromSlicesSingleSliceHalfExtentForManifold(plan) {
19656
19754
  }
19657
19755
  return Math.max(1, radius + maxOffsetMagnitude + plan.boundsPadding + plan.edgeLength * 3);
19658
19756
  }
19757
+ const BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG = 60;
19758
+ function promoteBooleanOperandNormals(shapes) {
19759
+ let maxExtra = 0;
19760
+ for (const shape of shapes) maxExtra = Math.max(maxExtra, shape.numProp());
19761
+ if (maxExtra < 3) return { operands: shapes, created: [] };
19762
+ const created = [];
19763
+ const operands = shapes.map((shape) => {
19764
+ if (shape.numProp() >= 3) return shape;
19765
+ const promoted = shape.calculateNormals(0, BOOLEAN_OPERAND_NORMAL_SHARP_ANGLE_DEG);
19766
+ created.push(promoted);
19767
+ return promoted;
19768
+ });
19769
+ return { operands, created };
19770
+ }
19659
19771
  function lowerShapeBooleanCompilePlan(plan, wasm) {
19660
19772
  const shapes = plan.shapes.map((shape) => lowerShapeCompilePlanToManifold(shape, wasm));
19661
19773
  if (shapes.length === 0) {
@@ -19664,16 +19776,18 @@ function lowerShapeBooleanCompilePlan(plan, wasm) {
19664
19776
  if (shapes.length === 1) {
19665
19777
  return shapes[0];
19666
19778
  }
19779
+ const { operands, created } = promoteBooleanOperandNormals(shapes);
19667
19780
  try {
19668
19781
  switch (plan.op) {
19669
19782
  case "union":
19670
- return wasm.Manifold.union(shapes);
19783
+ return wasm.Manifold.union(operands);
19671
19784
  case "difference":
19672
- return wasm.Manifold.difference(shapes);
19785
+ return wasm.Manifold.difference(operands);
19673
19786
  case "intersection":
19674
- return wasm.Manifold.intersection(shapes);
19787
+ return wasm.Manifold.intersection(operands);
19675
19788
  }
19676
19789
  } finally {
19790
+ disposeWasmObjects(created);
19677
19791
  disposeWasmObjects(shapes);
19678
19792
  }
19679
19793
  }
@@ -21023,12 +21137,16 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
21023
21137
  const { positions, normals, indices } = surface.tessellate(res, res);
21024
21138
  const thickness = plan.thickness;
21025
21139
  const numVerts = positions.length;
21026
- const allPositions = [];
21027
- for (const [x2, y2, z2] of positions) allPositions.push(x2, y2, z2);
21140
+ const vertProps = [];
21028
21141
  for (let i = 0; i < numVerts; i++) {
21029
21142
  const [x2, y2, z2] = positions[i];
21030
21143
  const [nx, ny, nz] = normals[i];
21031
- allPositions.push(x2 - nx * thickness, y2 - ny * thickness, z2 - nz * thickness);
21144
+ vertProps.push(x2, y2, z2, nx, ny, nz);
21145
+ }
21146
+ for (let i = 0; i < numVerts; i++) {
21147
+ const [x2, y2, z2] = positions[i];
21148
+ const [nx, ny, nz] = normals[i];
21149
+ vertProps.push(x2 - nx * thickness, y2 - ny * thickness, z2 - nz * thickness, -nx, -ny, -nz);
21032
21150
  }
21033
21151
  const allIndices = [];
21034
21152
  for (const idx of indices) allIndices.push(idx);
@@ -21053,11 +21171,12 @@ function lowerNurbsSurfaceToManifold(plan, wasm) {
21053
21171
  allIndices.push(c2, c2 + numVerts, d2, d2, c2 + numVerts, d2 + numVerts);
21054
21172
  }
21055
21173
  const mesh = new wasm.Mesh({
21056
- numProp: 3,
21057
- vertProperties: new Float32Array(allPositions),
21174
+ numProp: 6,
21175
+ vertProperties: new Float32Array(vertProps),
21058
21176
  triVerts: new Uint32Array(allIndices)
21059
21177
  });
21060
21178
  try {
21179
+ mesh.merge();
21061
21180
  return new wasm.Manifold(mesh);
21062
21181
  } finally {
21063
21182
  disposeWasmObject(mesh);
@@ -37485,6 +37604,7 @@ const _TruckShapeBackend = class _TruckShapeBackend {
37485
37604
  const payload = JSON.parse(getTruckGeometryWasm().geometry_mesh(this.getLiveHandle("getMesh()")));
37486
37605
  const numTri = payload.triangles.length / 3;
37487
37606
  const numVert = payload.positions.length / 3;
37607
+ const cornerNormals = payload.normals && payload.normals.length === numTri * 9 ? new Float32Array(payload.normals) : void 0;
37488
37608
  this.resource.mesh = {
37489
37609
  numProp: 3,
37490
37610
  numTri,
@@ -37498,7 +37618,8 @@ const _TruckShapeBackend = class _TruckShapeBackend {
37498
37618
  runTransform: new Float32Array([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0]),
37499
37619
  faceID: new Int32Array(payload.face_ids),
37500
37620
  faceIdNames: payload.face_id_names ?? [],
37501
- halfedgeTangent: new Float32Array(0)
37621
+ halfedgeTangent: new Float32Array(0),
37622
+ cornerNormals
37502
37623
  };
37503
37624
  return this.resource.mesh;
37504
37625
  }
@@ -38719,6 +38840,16 @@ function boundsInteriorOverlap(a2, b) {
38719
38840
  const tolerance = 1e-8;
38720
38841
  return Math.min(a2.max[0], b.max[0]) - Math.max(a2.min[0], b.min[0]) > tolerance && Math.min(a2.max[1], b.max[1]) - Math.max(a2.min[1], b.min[1]) > tolerance && Math.min(a2.max[2], b.max[2]) - Math.max(a2.min[2], b.min[2]) > tolerance;
38721
38842
  }
38843
+ function boundsFaceOrInteriorOverlap(a2, b) {
38844
+ const tolerance = 1e-8;
38845
+ const overlaps = [
38846
+ Math.min(a2.max[0], b.max[0]) - Math.max(a2.min[0], b.min[0]),
38847
+ Math.min(a2.max[1], b.max[1]) - Math.max(a2.min[1], b.min[1]),
38848
+ Math.min(a2.max[2], b.max[2]) - Math.max(a2.min[2], b.min[2])
38849
+ ];
38850
+ if (overlaps.some((overlap) => overlap < -tolerance)) return false;
38851
+ return overlaps.filter((overlap) => overlap <= tolerance).length <= 1;
38852
+ }
38722
38853
  function boundsFromPoints(points) {
38723
38854
  const first = points[0];
38724
38855
  if (!first) return null;
@@ -38801,16 +38932,16 @@ function shapePlanBounds(plan) {
38801
38932
  return null;
38802
38933
  }
38803
38934
  }
38804
- function hasPairwiseInteriorBoundsOverlap(shapes) {
38935
+ function hasPairwiseFaceOrInteriorBoundsOverlap(shapes) {
38805
38936
  const bounds = shapes.map((shape) => shape.boundingBox());
38806
38937
  for (let i = 0; i < bounds.length; i++) {
38807
38938
  for (let j = i + 1; j < bounds.length; j++) {
38808
- if (boundsInteriorOverlap(bounds[i], bounds[j])) return true;
38939
+ if (boundsFaceOrInteriorOverlap(bounds[i], bounds[j])) return true;
38809
38940
  }
38810
38941
  }
38811
38942
  return false;
38812
38943
  }
38813
- function interiorOverlapComponents(shapes) {
38944
+ function faceOrInteriorOverlapComponents(shapes) {
38814
38945
  const bounds = shapes.map((shape) => shape.boundingBox());
38815
38946
  const visited = /* @__PURE__ */ new Set();
38816
38947
  const components = [];
@@ -38824,7 +38955,7 @@ function interiorOverlapComponents(shapes) {
38824
38955
  component.push(current);
38825
38956
  for (let next = 0; next < shapes.length; next++) {
38826
38957
  if (visited.has(next)) continue;
38827
- if (boundsInteriorOverlap(bounds[current], bounds[next])) {
38958
+ if (boundsFaceOrInteriorOverlap(bounds[current], bounds[next])) {
38828
38959
  visited.add(next);
38829
38960
  queue.push(next);
38830
38961
  }
@@ -38869,10 +39000,10 @@ function lowerGenericBooleanPlan(plan) {
38869
39000
  let returned = null;
38870
39001
  try {
38871
39002
  if (plan.op === "union") {
38872
- if (!hasPairwiseInteriorBoundsOverlap(shapes)) {
39003
+ if (!hasPairwiseFaceOrInteriorBoundsOverlap(shapes)) {
38873
39004
  return null;
38874
39005
  }
38875
- const components = interiorOverlapComponents(shapes);
39006
+ const components = faceOrInteriorOverlapComponents(shapes);
38876
39007
  if (components.length > 1) {
38877
39008
  returned = lowerClusteredUnion(shapes, components);
38878
39009
  return returned;
@@ -39402,9 +39533,13 @@ function shapeHasClosedNativeTopology(shape) {
39402
39533
  function normalizeTruckShapeForBooleanInput(shape) {
39403
39534
  if (shapeHasClosedNativeTopology(shape)) return shape;
39404
39535
  if (!meshHasRawBoundaryEdges(shape.getMesh())) return shape;
39405
- const normalized = normalizeFacetedTruckShape(shape);
39406
- disposeShapeBackend(shape);
39407
- return normalized;
39536
+ try {
39537
+ const normalized = normalizeFacetedTruckShape(shape);
39538
+ disposeShapeBackend(shape);
39539
+ return normalized;
39540
+ } catch {
39541
+ return shape;
39542
+ }
39408
39543
  }
39409
39544
  function lowerSdfPlan(plan) {
39410
39545
  if (getUnsupportedSdfProgramReason(plan.tree) === void 0) {
@@ -41288,7 +41423,7 @@ function circleProfilePlan(radius, segments) {
41288
41423
  }
41289
41424
  function annulusProfilePlan(outerRadius, innerRadius, segments) {
41290
41425
  const outer = Math.abs(outerRadius);
41291
- const inner = Math.abs(innerRadius);
41426
+ const inner = Math.max(0, innerRadius);
41292
41427
  if (outer <= EXACT_PROFILE_EPS) return emptyProfilePlan();
41293
41428
  if (inner <= EXACT_PROFILE_EPS) return circleProfilePlan(outer, segments);
41294
41429
  return {
@@ -49960,7 +50095,7 @@ class Shape {
49960
50095
  *
49961
50096
  * Use `.color()` to set the base diffuse color; `.material()` controls how that color behaves
49962
50097
  * under light (metalness, roughness, clearcoat) and can add emissive glow independent of
49963
- * lighting. Emissive glow pairs naturally with the `postProcessing.bloom` effect in `scene()`.
50098
+ * lighting.
49964
50099
  *
49965
50100
  * **Example**
49966
50101
  *
@@ -51493,7 +51628,8 @@ class FrozenShape extends Shape {
51493
51628
  const EDGE_THRESHOLD_DOT = Math.cos(Math.PI / 180);
51494
51629
  const SMOOTH_THRESHOLD_DOT = Math.cos(30 * Math.PI / 180);
51495
51630
  function computeGeometryArrays(mesh, options = {}) {
51496
- const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals } = mesh;
51631
+ const { numProp, numTri: triCount, triVerts, vertProperties, vertNormals, cornerNormals } = mesh;
51632
+ const useCornerNormals = !!cornerNormals && cornerNormals.length === triCount * 9;
51497
51633
  const positions = new Float32Array(triCount * 9);
51498
51634
  const normals = new Float32Array(triCount * 9);
51499
51635
  const faceNx = new Float32Array(triCount);
@@ -51525,7 +51661,9 @@ function computeGeometryArrays(mesh, options = {}) {
51525
51661
  positions[o + 6] = cx;
51526
51662
  positions[o + 7] = cy;
51527
51663
  positions[o + 8] = cz;
51528
- if (vertNormals) {
51664
+ if (useCornerNormals) {
51665
+ for (let k2 = 0; k2 < 9; k2++) normals[o + k2] = cornerNormals[o + k2];
51666
+ } else if (vertNormals) {
51529
51667
  normals[o] = vertNormals[i0 * 3];
51530
51668
  normals[o + 1] = vertNormals[i0 * 3 + 1];
51531
51669
  normals[o + 2] = vertNormals[i0 * 3 + 2];
@@ -51568,7 +51706,7 @@ function computeGeometryArrays(mesh, options = {}) {
51568
51706
  faceNy[t] = fny;
51569
51707
  faceNz[t] = fnz;
51570
51708
  }
51571
- if (!vertNormals && numProp < 6 && triCount > 0) {
51709
+ if (!useCornerNormals && !vertNormals && numProp < 6 && triCount > 0) {
51572
51710
  computeAutoSmoothNormals(
51573
51711
  triVerts,
51574
51712
  vertProperties,
@@ -51755,7 +51893,8 @@ function shapeToGeometryFallback(shape) {
51755
51893
  vertProperties: mesh.vertProperties,
51756
51894
  mergeFromVert: mesh.mergeFromVert,
51757
51895
  mergeToVert: mesh.mergeToVert,
51758
- vertNormals
51896
+ vertNormals,
51897
+ cornerNormals: mesh.cornerNormals && mesh.cornerNormals.length === mesh.numTri * 9 ? mesh.cornerNormals : void 0
51759
51898
  });
51760
51899
  const solid = new BufferGeometry();
51761
51900
  solid.setAttribute("position", new BufferAttribute(positions, 3));