forgecad 0.10.1 → 0.10.2

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 (59) hide show
  1. package/dist/assets/{AdminPage-DcCnj0qo.js → AdminPage-CHY6ZN-p.js} +1 -1
  2. package/dist/assets/{BenchmarkPage-BVEpJSVk.js → BenchmarkPage-BcRT5iGN.js} +1 -1
  3. package/dist/assets/{BlogPage-DHaGP50_.js → BlogPage-BssBbnb-.js} +1 -1
  4. package/dist/assets/{DocsPage-CDoxHkz8.js → DocsPage-DsvdiRNK.js} +1 -1
  5. package/dist/assets/{EditorApp-BJ0Dloyh.js → EditorApp-Bfd3jbtC.js} +18 -16
  6. package/dist/assets/{EmbedViewer-CRKZbY0y.js → EmbedViewer-D5t8WamV.js} +3 -3
  7. package/dist/assets/{LandingPageProofDriven-BxHkYRE7.js → LandingPageProofDriven-DbN7o-Be.js} +1 -1
  8. package/dist/assets/{LegalPage-B-u6FrVv.js → LegalPage-DNGrrY0p.js} +1 -1
  9. package/dist/assets/{PricingPage-CzpZ6-Ce.js → PricingPage-Nczr3pRz.js} +1 -1
  10. package/dist/assets/{SettingsPage-CIZSSAd0.js → SettingsPage-DZlyu4d4.js} +1 -1
  11. package/dist/assets/{app-DaTMg3nH.js → app-C9ct2hRD.js} +1154 -368
  12. package/dist/assets/{scalar-sampling-budget-prBw_s8t.js → backendInit-ymjonyQp.js} +84920 -78304
  13. package/dist/assets/cli/{render-DPf4AYJK.js → render-B_0lQwKU.js} +35 -179
  14. package/dist/assets/{constructionHistoryWorker-AwMMWSxg.js → constructionHistoryWorker-CZ42Dksy.js} +8058 -1225
  15. package/dist/assets/{evalWorker-CjZZWRWW.js → evalWorker-C2pm8LHP.js} +21596 -14770
  16. package/dist/assets/{forgecad_geometry-Dgceylq9.js → forgecad_geometry-BlMtqluF.js} +120 -1
  17. package/dist/assets/{forgecad_geometry_bg-dD4RNQF1.wasm → forgecad_geometry_bg-BllP_WiL.wasm} +0 -0
  18. package/dist/assets/{inspectWorker-CZsCFtQT.js → inspectWorker-D5T5VbfK.js} +31375 -32603
  19. package/dist/assets/{jointPose-DzQOViQH.js → jointPose-4r8ed8_5.js} +1 -1
  20. package/dist/assets/{manifold-K1SkarlQ.js → manifold-5PP1eGLN.js} +1 -1
  21. package/dist/assets/{manifold-DgXo0T5P.js → manifold-C4r6B-XY.js} +2 -2
  22. package/dist/assets/{manifold-BYlzU521.js → manifold-DjBkyIc8.js} +1 -1
  23. package/dist/assets/{reportWorker-B9nWwSrB.js → reportWorker-CwenM7wB.js} +45719 -44425
  24. package/dist/cli/render.html +1 -1
  25. package/dist/docs/index.html +1 -1
  26. package/dist/docs-raw/CLI.md +27 -15
  27. package/dist/docs-raw/generated/assembly.md +1 -1
  28. package/dist/docs-raw/generated/concepts.md +1 -1
  29. package/dist/docs-raw/generated/core.md +1 -1
  30. package/dist/docs-raw/generated/lib.md +1 -1
  31. package/dist/docs-raw/generated/sdf.md +2 -2
  32. package/dist/docs-raw/guides/simready-quickstart.md +3 -1
  33. package/dist/index.html +1 -1
  34. package/dist/sitemap.xml +15 -15
  35. package/dist-cli/{check-compiler-II7NLPAB.js → check-compiler-SP7FAL7R.js} +1 -1
  36. package/dist-cli/{check-query-propagation-7462TR3R.js → check-query-propagation-BRLSHP22.js} +1 -1
  37. package/dist-cli/{chunk-UWTJCGXF.js → chunk-RQQ42YCP.js} +50429 -43409
  38. package/dist-cli/forgecad.js +3017 -1390
  39. package/dist-cli/{forgecad_geometry-QOQIIP53.js → forgecad_geometry-7TVSNVUB.js} +119 -0
  40. package/dist-cli/forgecad_geometry_bg.wasm +0 -0
  41. package/dist-skill/CONTEXT.md +13 -13
  42. package/dist-skill/docs/API/core/concepts.md +1 -1
  43. package/dist-skill/docs/CLI.md +27 -15
  44. package/dist-skill/docs/generated/assembly.md +1 -1
  45. package/dist-skill/docs/generated/core.md +1 -1
  46. package/dist-skill/docs/generated/lib.md +1 -1
  47. package/dist-skill/docs/generated/sdf.md +2 -2
  48. package/examples/api/gyroid-voronoi-blend.forge.js +1 -1
  49. package/examples/api/organic-noise-sculpture.forge.js +1 -1
  50. package/examples/api/sdf-circular-array-knurling.forge.js +1 -1
  51. package/examples/api/{sdf-custom-raymarch.forge.js → sdf-custom-field-mesh-preview.forge.js} +3 -4
  52. package/examples/api/sdf-materialize-tree.forge.js +2 -2
  53. package/examples/api/sdf-plain-return.forge.js +3 -2
  54. package/examples/api/sdf-shapes.forge.js +2 -2
  55. package/examples/api/sdf-surface-basket-weave.forge.js +2 -2
  56. package/examples/generative/twisted-lattice-tower.forge.js +1 -1
  57. package/examples/generative/voronoi-lampshade.forge.js +1 -1
  58. package/package.json +2 -2
  59. package/dist/assets/manifold-CzYf_iub.js +0 -3023
@@ -4,7 +4,7 @@ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { en
4
4
  var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
5
  var _a2;
6
6
  import { c as create, j as jsxRuntimeExports, r as reactExports, a as createWithEqualityFn, R as React, T as Tb, s as schedulerExports, b as clientExports, d as reactDomExports, u as useParams, e as useSearchParams, f as useNavigate, L as Link, g as useLocation, B as BrowserRouter, h as Routes, i as Route, N as Navigate } from "./vendor-react-6j1Kke-Y.js";
7
- import { _ as __vitePreload, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, S as Scene, a as PCFSoftShadowMap, V as VSMShadowMap, b as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, c as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, d as Layers, e as Color, f as RGBAFormat, U as UnsignedByteType, g as Vector3, h as Vector2, i as Clock, T as THREE, D as DoubleSide, j as REVISION, M as Mesh, I as IcosahedronGeometry, k as ShaderMaterial, l as Spherical, Q as Quaternion, m as MOUSE, n as TOUCH, o as Ray, p as Plane, q as DataTextureLoader, H as HalfFloatType, F as FloatType, r as DataUtils, t as LinearFilter, u as RedFormat, v as InstancedBufferGeometry, w as Float32BufferAttribute, x as InstancedInterleavedBuffer, y as InterleavedBufferAttribute, E as WireframeGeometry, G as Box3, J as Sphere, K as UniformsUtils, X as UniformsLib, Y as Vector4, Z as Line3, $ as Matrix4, a0 as MathUtils, a1 as Uniform, a2 as WebGLRenderTarget, a3 as DepthTexture, a4 as BackSide, a5 as ClampToEdgeWrapping, a6 as PlaneGeometry, a7 as UVMapping, a8 as DataTexture, a9 as Texture, aa as MeshBasicMaterial, ab as IntType, ac as ShortType, ad as ByteType, ae as UnsignedIntType, af as Loader, ag as LoadingManager, ah as LinearMipMapLinearFilter, ai as FileLoader, aj as NoBlending, ak as CubeReflectionMapping, al as EquirectangularReflectionMapping, am as CubeTextureLoader, an as WebGLCubeRenderTarget, ao as ConstraintSketch, ap as setSketchPlacement3D, aq as Sketch, ar as PROFILE_BACKEND_MARKER, as as FrozenShape, at as setShapeCompilePlan, au as hasAnyPorts, av as setShapePortsInternal, aw as markShapePortsUsed, ax as setParamOverrides, ay as resolveForgeRenderStyle, az as DEFAULT_ACTIVE_BACKEND, aA as isConstraintSketch, aB as updateConstraintValue, aC as linearizeMultiObjectSteps, aD as getShapeCompilePlan, aE as SCAN_PROXY_GRANULARITY_DEFAULT, aF as publishSolverWasmRunDebug, aG as resolveForgeQualityPreset, aH as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aI as findJointAnimationClip, aJ as resolveJointAnimation, aK as resolveJointViewValues, aL as resolveImportPath, aM as resolveScanProxyGranularity, aN as BufferGeometry, aO as LineBasicMaterial, aP as Line$1, aQ as LineDashedMaterial, aR as DepthStencilFormat, aS as UnsignedInt248Type, aT as MeshNormalMaterial, aU as NearestFilter, aV as BasicDepthPacking, aW as EventDispatcher$1, aX as NoColorSpace, aY as FrontSide, aZ as Material, a_ as AlwaysDepth, a$ as BufferAttribute, b0 as CanvasTexture, b1 as Object3D, b2 as FogExp2, b3 as Fog, b4 as AmbientLight, b5 as HemisphereLight, b6 as SpotLight, b7 as PointLight, b8 as DirectionalLight, b9 as heatPointsForSide, ba as COMPARISON_COLORS, bb as comparisonHeatDepthTest, bc as comparisonHeatEdgeOpacity, bd as comparisonHeatPatchOpacity, be as shapeToGeometry, bf as buildComparisonHeatPatchGeometry, bg as EdgesGeometry, bh as buildShapeFromCompilePlan, bi as buildVisibleHistoryStacks, bj as sketchToSvg, bk as sketchToDxf, bl as runScript, bm as MeshPhysicalMaterial, bn as LineSegments, bo as findDesignTraceNodeForConstructionStep, bp as formatDesignTraceAnchor, bq as waitForAnimationFrame, br as selectBuildLedgerNodes, bs as worldAuthorPlaneToLocal, bt as scanProxySourceBytes, bu as disposeScanProxyGeometry, bv as scanProxyGeometryFromPayload, bw as AdditiveBlending, bx as geometryWithVisibleVertexColors, by as getRenderStylePreset, bz as SCAN_RENDER_COLORS, bA as NormalBlending, bB as scanMaterialShellColor, bC as ZEBRA_STRIPE_SOFTNESS, bD as ZEBRA_STRIPE_SCALE, bE as ZEBRA_LIGHT_COLOR, bF as ZEBRA_DARK_COLOR, bG as ZEBRA_ACCENT_COLOR, bH as ZEBRA_STRIPE_FRAGMENT_SHADER, bI as ZEBRA_STRIPE_VERTEX_SHADER, bJ as SCAN_PROXY_LAYER_STYLES, bK as scanMaterialLayerStyles, bL as SURFACE_FIELD_FRAGMENT_SHADER, bM as SURFACE_FIELD_VERTEX_SHADER, bN as WORLD_UP, bO as CatmullRomCurve3, bP as TubeGeometry, bQ as THICKNESS_GRADIENT_COLORS, bR as ROUGHNESS_COLORS, bS as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, bT as PERFORMANCE_SAMPLE_INTERVAL_SEC, bU as formatPerformanceCount, bV as NON_TEXT_INPUT_TYPES, bW as MeshStandardMaterial, bX as compileSdfNode3, bY as buildSdfRaymarchFragmentShader, bZ as SDF_RAYMARCH_PROXY_VERTEX_SHADER, b_ as Shape, b$ as ShapeGeometry, c0 as ShaderLib, c1 as CylinderGeometry, c2 as VIEWPORT_CAMERA_STORAGE_KEY, c3 as parseViewportCameraState, c4 as createResolvedExplodeConfig, c5 as explodeBoundsCenter, c6 as explodeMergeBounds, c7 as resolveExplodeDirective, c8 as computeExplodeMotion, c9 as getSketchWorldMatrix, ca as explodeAdd, cb as hasExplodeOverride, cc as resolveExplodeLocalFanDirection, cd as explodeMul, ce as explodeLeafFanStage, cf as normalizeCutPlane, cg as toClippingPlane, ch as isObjectExcludedFromCutPlane, ci as getShapePorts, cj as getShapeUsedPorts, ck as DEFAULT_VIEW_CONFIG, cl as SECTION_EXPLORER_PLANE_NAME, cm as ZERO_OFFSET, cn as scanProxyGridForBounds, co as IDENTITY_MATRIX$2, cp as OBJECT_CONTEXT_MENU_MARGIN, cq as OBJECT_CONTEXT_MENU_WIDTH, cr as OBJECT_CONTEXT_MENU_HEIGHT, cs as getKernelFaceNameForTriangle, ct as triangleSoupFromMeshes, cu as compareTriangleSoups, cv as buildGeometryComparisonPointCloud, cw as aabbOverlaps, cx as aabbOverlapVolume, cy as DEFAULT_COLLISION_INSPECTION_OPTIONS, cz as resolveScalarSceneSampleBudget, cA as FOCUS_MODE_DIM_OPACITY, cB as comparisonCandidateContextOpacity, cC as Matrix3, cD as initKernel, cE as initSolverWasm } from "./scalar-sampling-budget-prBw_s8t.js";
7
+ import { _ as __vitePreload, S as SDF_PROGRAM_OP, z as zipSync, s as strToU8, W as WebGLRenderer, R as Raycaster, O as OrthographicCamera$1, P as PerspectiveCamera$1, a as Scene, b as PCFSoftShadowMap, V as VSMShadowMap, c as PCFShadowMap, B as BasicShadowMap, C as ColorManagement, L as LinearSRGBColorSpace, d as SRGBColorSpace, N as NoToneMapping, A as ACESFilmicToneMapping, e as Layers, f as Color, g as RGBAFormat, U as UnsignedByteType, h as Vector3, i as Vector2, j as Clock, T as THREE, D as DoubleSide, k as REVISION, M as Mesh, I as IcosahedronGeometry, l as ShaderMaterial, m as Spherical, Q as Quaternion, n as MOUSE, o as TOUCH, p as Ray, q as Plane, r as DataTextureLoader, H as HalfFloatType, F as FloatType, t as DataUtils, u as LinearFilter, v as RedFormat, w as InstancedBufferGeometry, x as Float32BufferAttribute, y as InstancedInterleavedBuffer, E as InterleavedBufferAttribute, G as WireframeGeometry, J as Box3, K as Sphere, X as UniformsUtils, Y as UniformsLib, Z as Vector4, $ as Line3, a0 as Matrix4, a1 as MathUtils, a2 as Uniform, a3 as WebGLRenderTarget, a4 as DepthTexture, a5 as BackSide, a6 as ClampToEdgeWrapping, a7 as PlaneGeometry, a8 as UVMapping, a9 as DataTexture, aa as Texture, ab as MeshBasicMaterial, ac as IntType, ad as ShortType, ae as ByteType, af as UnsignedIntType, ag as Loader, ah as LoadingManager, ai as LinearMipMapLinearFilter, aj as FileLoader, ak as NoBlending, al as CubeReflectionMapping, am as EquirectangularReflectionMapping, an as CubeTextureLoader, ao as WebGLCubeRenderTarget, ap as ConstraintSketch, aq as setSketchPlacement3D, ar as Sketch, as as PROFILE_BACKEND_MARKER, at as FrozenShape, au as setShapeCompilePlan, av as hasAnyPorts, aw as setShapePortsInternal, ax as markShapePortsUsed, ay as setParamOverrides, az as resolveForgeRenderStyle, aA as DEFAULT_ACTIVE_BACKEND, aB as isConstraintSketch, aC as updateConstraintValue, aD as linearizeMultiObjectSteps, aE as getShapeCompilePlan, aF as SCAN_PROXY_GRANULARITY_DEFAULT, aG as publishSolverWasmRunDebug, aH as resolveForgeQualityPreset, aI as SCAN_PROXY_MATRIX_GRANULARITY_MAX, aJ as findJointAnimationClip, aK as resolveJointAnimation, aL as resolveJointViewValues, aM as resolveImportPath, aN as resolveScanProxyGranularity, aO as BufferGeometry, aP as LineBasicMaterial, aQ as Line$1, aR as LineDashedMaterial, aS as DepthStencilFormat, aT as UnsignedInt248Type, aU as MeshNormalMaterial, aV as NearestFilter, aW as BasicDepthPacking, aX as EventDispatcher$1, aY as NoColorSpace, aZ as FrontSide, a_ as Material, a$ as AlwaysDepth, b0 as BufferAttribute, b1 as CanvasTexture, b2 as Object3D, b3 as FogExp2, b4 as Fog, b5 as AmbientLight, b6 as HemisphereLight, b7 as SpotLight, b8 as PointLight, b9 as DirectionalLight, ba as heatPointsForSide, bb as COMPARISON_COLORS, bc as comparisonHeatDepthTest, bd as comparisonHeatEdgeOpacity, be as comparisonHeatPatchOpacity, bf as shapeToGeometry, bg as buildComparisonHeatPatchGeometry, bh as EdgesGeometry, bi as buildShapeFromCompilePlan, bj as buildVisibleHistoryStacks, bk as sketchToSvg, bl as sketchToDxf, bm as runScript, bn as MeshPhysicalMaterial, bo as LineSegments, bp as findDesignTraceNodeForConstructionStep, bq as formatDesignTraceAnchor, br as waitForAnimationFrame, bs as selectBuildLedgerNodes, bt as worldAuthorPlaneToLocal, bu as compileSdfProgramEvaluator3, bv as SDF_LINEAR_OUTPUT_COLOR_GLSL, bw as GLSL3, bx as BoxGeometry, by as Data3DTexture, bz as buildSdfRaymarchFragmentShader, bA as SDF_RAYMARCH_PROXY_VERTEX_SHADER, bB as scanProxySourceBytes, bC as disposeScanProxyGeometry, bD as scanProxyGeometryFromPayload, bE as AdditiveBlending, bF as geometryWithVisibleVertexColors, bG as getRenderStylePreset, bH as SCAN_RENDER_COLORS, bI as NormalBlending, bJ as scanMaterialShellColor, 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, bP as ZEBRA_STRIPE_FRAGMENT_SHADER, bQ as ZEBRA_STRIPE_VERTEX_SHADER, bR as SCAN_PROXY_LAYER_STYLES, bS as scanMaterialLayerStyles, bT as SURFACE_FIELD_FRAGMENT_SHADER, bU as SURFACE_FIELD_VERTEX_SHADER, bV as WORLD_UP, bW as CatmullRomCurve3, bX as TubeGeometry, bY as THICKNESS_GRADIENT_COLORS, bZ as ROUGHNESS_COLORS, b_ as DEFAULT_ROUGHNESS_INSPECTION_OPTIONS, b$ as PERFORMANCE_SAMPLE_INTERVAL_SEC, c0 as formatPerformanceCount, c1 as NON_TEXT_INPUT_TYPES, c2 as MeshStandardMaterial, c3 as Shape, c4 as ShapeGeometry, c5 as ShaderLib, c6 as CylinderGeometry, c7 as VIEWPORT_CAMERA_STORAGE_KEY, c8 as parseViewportCameraState, c9 as createResolvedExplodeConfig, ca as explodeBoundsCenter, cb as explodeMergeBounds, cc as resolveExplodeDirective, cd as computeExplodeMotion, ce as getSketchWorldMatrix, cf as explodeAdd, cg as hasExplodeOverride, ch as resolveExplodeLocalFanDirection, ci as explodeMul, cj as explodeLeafFanStage, ck as normalizeCutPlane, cl as toClippingPlane, cm as isObjectExcludedFromCutPlane, cn as getShapePorts, co as getShapeUsedPorts, cp as DEFAULT_VIEW_CONFIG, cq as SECTION_EXPLORER_PLANE_NAME, cr as ZERO_OFFSET, cs as scanProxyGridForBounds, ct as IDENTITY_MATRIX$2, cu as OBJECT_CONTEXT_MENU_MARGIN, cv as OBJECT_CONTEXT_MENU_WIDTH, cw as OBJECT_CONTEXT_MENU_HEIGHT, cx as getKernelFaceNameForTriangle, cy as triangleSoupFromMeshes, cz as compareTriangleSoups, cA as buildGeometryComparisonPointCloud, cB as aabbOverlaps, cC as aabbOverlapVolume, cD as DEFAULT_COLLISION_INSPECTION_OPTIONS, cE as resolveScalarSceneSampleBudget, cF as FOCUS_MODE_DIM_OPACITY, cG as comparisonCandidateContextOpacity, cH as Matrix3, cI as initBackendForEvaluation } from "./backendInit-ymjonyQp.js";
8
8
  function getCsrfToken() {
9
9
  const match = document.cookie.match(/(?:^|;\s*)fc-csrf-token=([^;]+)/);
10
10
  return match == null ? void 0 : match[1];
@@ -880,6 +880,136 @@ function detectMobile() {
880
880
  return hasTouch && isNarrow;
881
881
  }
882
882
  const isMobile = detectMobile();
883
+ function buildSdfProgramWgslFunction(program, options = {}) {
884
+ const functionName = requireWgslIdentifier(options.functionName ?? "sdfProgram", "functionName");
885
+ const lines = [`fn ${functionName}(p: vec3f) -> f32 {`, " var v0: f32 = p.x;", " var v1: f32 = p.y;", " var v2: f32 = p.z;"];
886
+ const { opcodes, argA, argB, argC, constants, output } = program;
887
+ for (let index = 0; index < opcodes.length; index += 1) {
888
+ const slot = `v${index + 3}`;
889
+ const a2 = `v${argA[index]}`;
890
+ const b2 = `v${argB[index]}`;
891
+ switch (opcodes[index]) {
892
+ case SDF_PROGRAM_OP.Const:
893
+ lines.push(` var ${slot}: f32 = ${wgslNumber(constants[argC[index]] ?? 0)};`);
894
+ break;
895
+ case SDF_PROGRAM_OP.Neg:
896
+ lines.push(` var ${slot}: f32 = -${a2};`);
897
+ break;
898
+ case SDF_PROGRAM_OP.Abs:
899
+ lines.push(` var ${slot}: f32 = abs(${a2});`);
900
+ break;
901
+ case SDF_PROGRAM_OP.Sqrt:
902
+ lines.push(` var ${slot}: f32 = sqrt(${a2});`);
903
+ break;
904
+ case SDF_PROGRAM_OP.Sin:
905
+ lines.push(` var ${slot}: f32 = sin(${a2});`);
906
+ break;
907
+ case SDF_PROGRAM_OP.Cos:
908
+ lines.push(` var ${slot}: f32 = cos(${a2});`);
909
+ break;
910
+ case SDF_PROGRAM_OP.Round:
911
+ lines.push(` var ${slot}: f32 = round(${a2});`);
912
+ break;
913
+ case SDF_PROGRAM_OP.Add:
914
+ lines.push(` var ${slot}: f32 = ${a2} + ${b2};`);
915
+ break;
916
+ case SDF_PROGRAM_OP.Sub:
917
+ lines.push(` var ${slot}: f32 = ${a2} - ${b2};`);
918
+ break;
919
+ case SDF_PROGRAM_OP.Mul:
920
+ lines.push(` var ${slot}: f32 = ${a2} * ${b2};`);
921
+ break;
922
+ case SDF_PROGRAM_OP.Div:
923
+ lines.push(` var ${slot}: f32 = ${a2} / ${b2};`);
924
+ break;
925
+ case SDF_PROGRAM_OP.Min:
926
+ lines.push(` var ${slot}: f32 = min(${a2}, ${b2});`);
927
+ break;
928
+ case SDF_PROGRAM_OP.Max:
929
+ lines.push(` var ${slot}: f32 = max(${a2}, ${b2});`);
930
+ break;
931
+ case SDF_PROGRAM_OP.Atan2:
932
+ lines.push(` var ${slot}: f32 = atan2(${a2}, ${b2});`);
933
+ break;
934
+ case SDF_PROGRAM_OP.LessThan:
935
+ lines.push(` var ${slot}: f32 = select(0.0, 1.0, ${a2} < ${b2});`);
936
+ break;
937
+ case SDF_PROGRAM_OP.Mod:
938
+ lines.push(` var ${slot}: f32 = ${a2} % ${b2};`);
939
+ break;
940
+ default:
941
+ throw new Error(`Unknown SdfProgram opcode ${opcodes[index]} at instruction ${index}.`);
942
+ }
943
+ }
944
+ lines.push(` return v${output};`);
945
+ lines.push("}");
946
+ return lines.join("\n");
947
+ }
948
+ function buildSdfProgramBrickComputeWgsl(program, options = {}) {
949
+ const functionName = requireWgslIdentifier(options.functionName ?? "sdfProgram", "functionName");
950
+ const entryPoint = requireWgslIdentifier(options.entryPoint ?? "sampleSdfBrick", "entryPoint");
951
+ const [workgroupX, workgroupY, workgroupZ] = requireWorkgroupSize(options.workgroupSize ?? [4, 4, 4]);
952
+ const bindingGroup = requireBindingIndex(options.bindingGroup ?? 0, "bindingGroup");
953
+ const paramsBinding = requireBindingIndex(options.paramsBinding ?? 0, "paramsBinding");
954
+ const distancesBinding = requireBindingIndex(options.distancesBinding ?? 1, "distancesBinding");
955
+ return [
956
+ buildSdfProgramWgslFunction(program, { functionName }),
957
+ "",
958
+ "struct SdfBrickParams {",
959
+ " origin: vec4f,",
960
+ " step: vec4f,",
961
+ " dims: vec4u,",
962
+ "};",
963
+ "",
964
+ `@group(${bindingGroup}) @binding(${paramsBinding}) var<uniform> brick: SdfBrickParams;`,
965
+ `@group(${bindingGroup}) @binding(${distancesBinding}) var<storage, read_write> distances: array<f32>;`,
966
+ "",
967
+ `@compute @workgroup_size(${workgroupX}, ${workgroupY}, ${workgroupZ})`,
968
+ `fn ${entryPoint}(@builtin(global_invocation_id) gid: vec3u) {`,
969
+ " let dims = brick.dims.xyz;",
970
+ " if (any(gid >= dims)) {",
971
+ " return;",
972
+ " }",
973
+ " let index = gid.x + dims.x * (gid.y + dims.y * gid.z);",
974
+ " let voxel = vec3f(f32(gid.x), f32(gid.y), f32(gid.z));",
975
+ " let p = brick.origin.xyz + voxel * brick.step.xyz;",
976
+ ` distances[index] = ${functionName}(p);`,
977
+ "}"
978
+ ].join("\n");
979
+ }
980
+ function wgslNumber(value) {
981
+ if (!Number.isFinite(value)) return "0.0";
982
+ const text = Number(value.toPrecision(9)).toString();
983
+ return text.includes(".") || text.includes("e") ? text : `${text}.0`;
984
+ }
985
+ function requireWgslIdentifier(value, name) {
986
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(value)) {
987
+ throw new Error(`${name} must be a valid WGSL identifier.`);
988
+ }
989
+ return value;
990
+ }
991
+ function requireWorkgroupSize(value) {
992
+ const [x, y, z] = value;
993
+ for (const [name, size] of [
994
+ ["x", x],
995
+ ["y", y],
996
+ ["z", z]
997
+ ]) {
998
+ if (!Number.isInteger(size) || size < 1 || size > 256) {
999
+ throw new Error(`workgroupSize.${name} must be an integer from 1 to 256.`);
1000
+ }
1001
+ }
1002
+ if (x * y * z > 256) {
1003
+ throw new Error("workgroupSize product must be at most 256 for portable WebGPU compute.");
1004
+ }
1005
+ return [x, y, z];
1006
+ }
1007
+ function requireBindingIndex(value, name) {
1008
+ if (!Number.isInteger(value) || value < 0) {
1009
+ throw new Error(`${name} must be a non-negative integer.`);
1010
+ }
1011
+ return value;
1012
+ }
883
1013
  function escapeXml(value) {
884
1014
  return value.replace(/[^\u0009\u000A\u000D\u0020-\uD7FF\uE000-\uFFFD]/g, " ").replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
885
1015
  }
@@ -15157,8 +15287,12 @@ const runResultDeserializer = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Obje
15157
15287
  __proto__: null,
15158
15288
  deserializeRunResult
15159
15289
  }, Symbol.toStringTag, { value: "Module" }));
15290
+ function compilePlanNeedsNativeShapeLowering(plan) {
15291
+ if (plan.kind === "queryOwner") return compilePlanNeedsNativeShapeLowering(plan.base);
15292
+ return plan.kind !== "sdf";
15293
+ }
15160
15294
  function serializedSceneObjectNeedsNativeShapeLowering(object) {
15161
- return object.compilePlan !== null && object.compilePlan !== void 0;
15295
+ return object.compilePlan !== null && object.compilePlan !== void 0 && compilePlanNeedsNativeShapeLowering(object.compilePlan);
15162
15296
  }
15163
15297
  function serializedRunResultNeedsNativeOcctLowering(result) {
15164
15298
  return result.objects.some(serializedSceneObjectNeedsNativeShapeLowering);
@@ -15841,7 +15975,7 @@ const CRASH_COOLDOWN_MS = 2e3;
15841
15975
  class EvalWorkerClient {
15842
15976
  constructor(workerFactory = () => new Worker(new URL(
15843
15977
  /* @vite-ignore */
15844
- "/assets/evalWorker-CjZZWRWW.js",
15978
+ "/assets/evalWorker-C2pm8LHP.js",
15845
15979
  import.meta.url
15846
15980
  ), { type: "module" })) {
15847
15981
  __publicField(this, "worker", null);
@@ -17327,6 +17461,8 @@ function formatComputeBackendLabel(backend) {
17327
17461
  return "Local OCCT";
17328
17462
  case "truck":
17329
17463
  return "Local Truck";
17464
+ case "sdf":
17465
+ return "Local SDF";
17330
17466
  case "server-occt":
17331
17467
  return "Server OCCT";
17332
17468
  }
@@ -17640,6 +17776,28 @@ function resolveViewportScanGranularity(granularity, renderStyle) {
17640
17776
  return renderStyle === "matrix" ? Math.min(next, SCAN_PROXY_MATRIX_GRANULARITY_MAX) : next;
17641
17777
  }
17642
17778
  const initialPreviewFile = resolvePreviewFile(initialActive, INITIAL_FILES);
17779
+ function openCodeEditorPatch() {
17780
+ writeViewPreferences({ codeEditorOpen: true });
17781
+ return { codeEditorOpen: true };
17782
+ }
17783
+ function activeFileHasViewportPreview(activeFile, files, meshPreviewFile) {
17784
+ if (meshPreviewFile) return true;
17785
+ if (!activeFile || !(activeFile in files)) return false;
17786
+ if (resolvePreviewFile(activeFile, files)) return true;
17787
+ return activeFile.toLowerCase().endsWith(".svg");
17788
+ }
17789
+ function codeEditorPatchForActiveFile(activeFile, files, meshPreviewFile) {
17790
+ if (!activeFile || !(activeFile in files)) return {};
17791
+ return activeFileHasViewportPreview(activeFile, files, meshPreviewFile) ? {} : openCodeEditorPatch();
17792
+ }
17793
+ function runResultHasViewportContent(result) {
17794
+ var _a3, _b2, _c, _d;
17795
+ return result.objects.length > 0 || !!result.assemblyKinematics || (((_a3 = result.jointsView) == null ? void 0 : _a3.joints.length) ?? 0) > 0 && ((_b2 = result.jointsView) == null ? void 0 : _b2.enabled) !== false || (((_c = result.renderLabels) == null ? void 0 : _c.length) ?? 0) > 0 || (((_d = result.debugHighlights3D) == null ? void 0 : _d.length) ?? 0) > 0;
17796
+ }
17797
+ function codeEditorPatchForRunResult(result, meshPreviewFile) {
17798
+ if (meshPreviewFile || runResultHasViewportContent(result)) return {};
17799
+ return openCodeEditorPatch();
17800
+ }
17643
17801
  const initialObjectSettingsByFile = (() => {
17644
17802
  const viewPreferences = initialViewPreferences;
17645
17803
  if (viewPreferences.objectSettingsByFile && typeof viewPreferences.objectSettingsByFile === "object") {
@@ -17819,6 +17977,7 @@ const useForgeStore = create((set, get) => ({
17819
17977
  const restored = targetPreviewFile && nextByFile[targetPreviewFile] ? nextByFile[targetPreviewFile] : {};
17820
17978
  set({
17821
17979
  activeFile: name,
17980
+ ...codeEditorPatchForActiveFile(name, files, null),
17822
17981
  recentFiles: touchAndPersistRecentFile(get().recentFiles, files, name),
17823
17982
  meshPreviewFile: null,
17824
17983
  // Clear mesh preview when switching files
@@ -17869,11 +18028,13 @@ const useForgeStore = create((set, get) => ({
17869
18028
  if (get().files[normalized] !== void 0) return;
17870
18029
  if (get().folders.includes(normalized)) return;
17871
18030
  const template = projectTextFileTemplate(normalized);
18031
+ const nextFilesForEditor = { ...get().files, [normalized]: template };
17872
18032
  const newFolders = Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort();
17873
18033
  set((s) => ({
17874
18034
  files: { ...s.files, [normalized]: template },
17875
18035
  savedFiles: { ...s.savedFiles, [normalized]: template },
17876
18036
  activeFile: normalized,
18037
+ ...codeEditorPatchForActiveFile(normalized, nextFilesForEditor, null),
17877
18038
  recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: template }, normalized),
17878
18039
  paramOverrides: {},
17879
18040
  jointValues: {},
@@ -17912,6 +18073,7 @@ const useForgeStore = create((set, get) => ({
17912
18073
  files: remaining,
17913
18074
  savedFiles: remainingSaved,
17914
18075
  activeFile: newActive,
18076
+ ...codeEditorPatchForActiveFile(newActive, remaining, null),
17915
18077
  recentFiles: touchAndPersistRecentFile(get().recentFiles, remaining, newActive),
17916
18078
  objectSettingsByFile: nextObjectSettingsByFile,
17917
18079
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -17942,10 +18104,12 @@ const useForgeStore = create((set, get) => ({
17942
18104
  const nextObjectSettingsByFile = remapObjectSettingsByFile(objectSettingsByFile, oldName, normalized);
17943
18105
  const nextParamSnapshotsByFile = remapParamSnapshotsByFile(paramSnapshotsByFile, oldName, normalized);
17944
18106
  writeViewPreferences({ objectSettingsByFile: nextObjectSettingsByFile, paramSnapshotsByFile: nextParamSnapshotsByFile });
18107
+ const nextActive = oldName === activeFile ? normalized : activeFile;
17945
18108
  set({
17946
18109
  files: remaining,
17947
18110
  savedFiles: remainingSaved,
17948
- activeFile: oldName === activeFile ? normalized : activeFile,
18111
+ activeFile: nextActive,
18112
+ ...codeEditorPatchForActiveFile(nextActive, remaining, null),
17949
18113
  recentFiles: remapAndPersistRecentFiles(get().recentFiles, remaining, oldName, normalized),
17950
18114
  objectSettingsByFile: nextObjectSettingsByFile,
17951
18115
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -18003,6 +18167,7 @@ const useForgeStore = create((set, get) => ({
18003
18167
  savedFiles: updatedSaved,
18004
18168
  folders: updatedFolders,
18005
18169
  activeFile: nextActive,
18170
+ ...codeEditorPatchForActiveFile(nextActive, updatedFiles, null),
18006
18171
  recentFiles: remapAndPersistRecentFiles(get().recentFiles, updatedFiles, normalizedOld, normalizedNew),
18007
18172
  objectSettingsByFile: nextObjectSettingsByFile,
18008
18173
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -18061,6 +18226,7 @@ const useForgeStore = create((set, get) => ({
18061
18226
  savedFiles: remainingSaved,
18062
18227
  folders: remainingFolders,
18063
18228
  activeFile: newActive,
18229
+ ...codeEditorPatchForActiveFile(newActive, remainingFiles, null),
18064
18230
  recentFiles: touchAndPersistRecentFile(get().recentFiles, remainingFiles, newActive),
18065
18231
  objectSettingsByFile: nextObjectSettingsByFile,
18066
18232
  paramSnapshotsByFile: nextParamSnapshotsByFile,
@@ -18215,7 +18381,14 @@ const useForgeStore = create((set, get) => ({
18215
18381
  if (cached) {
18216
18382
  const applied = buildRunState(previewFile, cached, get());
18217
18383
  rememberAcceptedAssemblyPoseState(cached, readAssemblyPoseControlState(applied.nextState));
18218
- set({ ...applied.nextState, lastValidResult: cached, isEvaluating: false, evaluationPhase: "idle", buildLedgerEvents: [] });
18384
+ set({
18385
+ ...applied.nextState,
18386
+ ...codeEditorPatchForRunResult(cached, meshPreviewFile),
18387
+ lastValidResult: cached,
18388
+ isEvaluating: false,
18389
+ evaluationPhase: "idle",
18390
+ buildLedgerEvents: []
18391
+ });
18219
18392
  writeViewPreferences({ objectSettingsByFile: applied.nextObjectSettingsByFile, cutPlaneEnabled: applied.nextCutPlaneEnabled });
18220
18393
  return;
18221
18394
  }
@@ -18267,10 +18440,12 @@ const useForgeStore = create((set, get) => ({
18267
18440
  const message = serverError instanceof Error ? serverError.message : String(serverError);
18268
18441
  if (execId !== currentExecutionId) return;
18269
18442
  rememberAcceptedAssemblyPoseState(null, EMPTY_ASSEMBLY_POSE_CONTROL_STATE);
18443
+ const failedResult = errorRunResult(`Server compute failed: ${message}`, performance.now() - tDispatch);
18270
18444
  set({
18271
- result: errorRunResult(`Server compute failed: ${message}`, performance.now() - tDispatch),
18445
+ result: failedResult,
18272
18446
  consoleLogs: [],
18273
18447
  previewFile,
18448
+ ...codeEditorPatchForRunResult(failedResult, meshPreviewFile),
18274
18449
  isEvaluating: false,
18275
18450
  evaluationPhase: "idle",
18276
18451
  buildLedgerEvents: []
@@ -18301,12 +18476,25 @@ const useForgeStore = create((set, get) => ({
18301
18476
  );
18302
18477
  if (runResult.error) {
18303
18478
  rememberAcceptedAssemblyPoseState(null, EMPTY_ASSEMBLY_POSE_CONTROL_STATE);
18304
- set({ result: runResult, consoleLogs: runResult.logs, previewFile, isEvaluating: false, evaluationPhase: "idle" });
18479
+ set({
18480
+ result: runResult,
18481
+ consoleLogs: runResult.logs,
18482
+ previewFile,
18483
+ ...codeEditorPatchForRunResult(runResult, meshPreviewFile),
18484
+ isEvaluating: false,
18485
+ evaluationPhase: "idle"
18486
+ });
18305
18487
  } else {
18306
18488
  storeCache(previewFile, code, files, paramOverrides, assemblyState, runQuality, executionBackend, runResult, serialized);
18307
18489
  const applied = buildRunState(previewFile, runResult, get());
18308
18490
  rememberAcceptedAssemblyPoseState(runResult, readAssemblyPoseControlState(applied.nextState));
18309
- set({ ...applied.nextState, lastValidResult: runResult, isEvaluating: false, evaluationPhase: "idle" });
18491
+ set({
18492
+ ...applied.nextState,
18493
+ ...codeEditorPatchForRunResult(runResult, meshPreviewFile),
18494
+ lastValidResult: runResult,
18495
+ isEvaluating: false,
18496
+ evaluationPhase: "idle"
18497
+ });
18310
18498
  writeViewPreferences({ objectSettingsByFile: applied.nextObjectSettingsByFile, cutPlaneEnabled: applied.nextCutPlaneEnabled });
18311
18499
  }
18312
18500
  } catch (error) {
@@ -18315,7 +18503,14 @@ const useForgeStore = create((set, get) => ({
18315
18503
  const message = error instanceof Error ? error.message : String(error);
18316
18504
  const errResult = createErrorRunResult(message, runQuality);
18317
18505
  rememberAcceptedAssemblyPoseState(null, EMPTY_ASSEMBLY_POSE_CONTROL_STATE);
18318
- set({ result: errResult, consoleLogs: errResult.logs, previewFile, isEvaluating: false, evaluationPhase: "idle" });
18506
+ set({
18507
+ result: errResult,
18508
+ consoleLogs: errResult.logs,
18509
+ previewFile,
18510
+ ...codeEditorPatchForRunResult(errResult, meshPreviewFile),
18511
+ isEvaluating: false,
18512
+ evaluationPhase: "idle"
18513
+ });
18319
18514
  }
18320
18515
  },
18321
18516
  updateAssemblyPose: async () => {
@@ -19304,10 +19499,12 @@ const useForgeStore = create((set, get) => ({
19304
19499
  loadFromText: (text, name) => {
19305
19500
  const normalized = normalizePath(name);
19306
19501
  const newFolders = Array.from(/* @__PURE__ */ new Set([...get().folders, ...collectParentPaths(normalized)])).sort();
19502
+ const nextFilesForEditor = { ...get().files, [normalized]: text };
19307
19503
  set((s) => ({
19308
19504
  files: { ...s.files, [normalized]: text },
19309
19505
  savedFiles: { ...s.savedFiles, [normalized]: text },
19310
19506
  activeFile: normalized,
19507
+ ...codeEditorPatchForActiveFile(normalized, nextFilesForEditor, null),
19311
19508
  recentFiles: touchAndPersistRecentFile(s.recentFiles, { ...s.files, [normalized]: text }, normalized),
19312
19509
  fileHandle: null,
19313
19510
  dirty: false,
@@ -19367,6 +19564,7 @@ const useForgeStore = create((set, get) => ({
19367
19564
  return {
19368
19565
  files: nextFiles,
19369
19566
  activeFile: nextActive,
19567
+ ...codeEditorPatchForActiveFile(nextActive, nextFiles, nextMeshPreview),
19370
19568
  recentFiles: touchAndPersistRecentFile(s.recentFiles, nextFiles, nextActive),
19371
19569
  fileHandle: null,
19372
19570
  dirty: true,
@@ -19458,6 +19656,7 @@ const useForgeStore = create((set, get) => ({
19458
19656
  set({
19459
19657
  ...nextState,
19460
19658
  activeFile: newActiveFile,
19659
+ ...codeEditorPatchForActiveFile(newActiveFile, nextFiles, nextState.meshPreviewFile),
19461
19660
  recentFiles: touchAndPersistRecentFile(baseRecentFiles, nextFiles, newActiveFile),
19462
19661
  filesLoading: false
19463
19662
  });
@@ -19494,6 +19693,7 @@ const useForgeStore = create((set, get) => ({
19494
19693
  set({
19495
19694
  ...nextState,
19496
19695
  activeFile: newActiveFile,
19696
+ ...codeEditorPatchForActiveFile(newActiveFile, nextState.files, null),
19497
19697
  recentFiles: touchAndPersistRecentFile(state2.recentFiles, nextState.files, newActiveFile)
19498
19698
  });
19499
19699
  if (newActiveFile && newActiveFile !== prevActiveFile) {
@@ -27660,7 +27860,7 @@ function ConstructionGhostOverlay({ matrix }) {
27660
27860
  class ConstructionHistoryWorkerClient {
27661
27861
  constructor(workerFactory = () => new Worker(new URL(
27662
27862
  /* @vite-ignore */
27663
- "/assets/constructionHistoryWorker-AwMMWSxg.js",
27863
+ "/assets/constructionHistoryWorker-CZ42Dksy.js",
27664
27864
  import.meta.url
27665
27865
  ), { type: "module" })) {
27666
27866
  __publicField(this, "worker", null);
@@ -30172,7 +30372,7 @@ function generateReportInWorker(options) {
30172
30372
  return new Promise((resolve2, reject) => {
30173
30373
  const worker = new Worker(new URL(
30174
30374
  /* @vite-ignore */
30175
- "/assets/reportWorker-B9nWwSrB.js",
30375
+ "/assets/reportWorker-CwenM7wB.js",
30176
30376
  import.meta.url
30177
30377
  ), { type: "module" });
30178
30378
  const cleanup = () => {
@@ -30377,7 +30577,7 @@ const SURFACE_PALETTE = ["#4488cc", "#44cc88", "#cc8844", "#cc44aa", "#88cc44",
30377
30577
  function toPage(tx, x, y) {
30378
30578
  return [x * tx.scale + tx.offsetX, y * tx.scale + tx.offsetY];
30379
30579
  }
30380
- function computeBounds$1(meta) {
30580
+ function computeBounds(meta) {
30381
30581
  const bounds = { min: [Infinity, Infinity], max: [-Infinity, -Infinity] };
30382
30582
  function expand(x, y) {
30383
30583
  bounds.min[0] = Math.min(bounds.min[0], x);
@@ -30443,7 +30643,7 @@ function computeBounds$1(meta) {
30443
30643
  return bounds;
30444
30644
  }
30445
30645
  function computeTransform(meta, ptsPerMm, marginPts) {
30446
- const bounds = computeBounds$1(meta);
30646
+ const bounds = computeBounds(meta);
30447
30647
  const sketchW = bounds.max[0] - bounds.min[0];
30448
30648
  const sketchH = bounds.max[1] - bounds.min[1];
30449
30649
  return {
@@ -30739,7 +30939,7 @@ function generateSketchPdf(meta, options) {
30739
30939
  const tx = computeTransform(meta, POINTS_PER_MM, MARGIN);
30740
30940
  tx.pageWidth = Math.max(tx.pageWidth, 600);
30741
30941
  tx.pageHeight = Math.max(tx.pageHeight, 400);
30742
- const bounds = computeBounds$1(meta);
30942
+ const bounds = computeBounds(meta);
30743
30943
  const extent = Math.max(bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], 1);
30744
30944
  const baseExtent = 100;
30745
30945
  const autoFontScale = Math.max(1, extent / baseExtent);
@@ -30919,9 +31119,14 @@ async function exportMeshFromStore(format, preferredStem, options = {}) {
30919
31119
  downloadExportArtifact(await buildMeshExportArtifact(format, stem, runResult, objectSettings));
30920
31120
  }
30921
31121
  async function exportExactFromStore(format, preferredStem) {
30922
- const { result, activeFile, files, paramOverrides, runQuality } = useForgeStore.getState();
31122
+ const { result, activeFile, files, paramOverrides, runQuality, activeBackend } = useForgeStore.getState();
30923
31123
  const assemblyState = resolveAssemblyStateForExport();
30924
31124
  requireSuccessfulRunResult(result);
31125
+ if (activeBackend === "sdf") {
31126
+ throw new Error(
31127
+ "STEP/BREP exact export is not available from the SDF backend. Use 3MF, OBJ, or STL mesh export, or switch to OCCT for exact B-rep export."
31128
+ );
31129
+ }
30925
31130
  const stem = sanitizeExportStem(preferredStem ?? deriveExportStem(activeFile));
30926
31131
  const code = files[activeFile];
30927
31132
  if (!code) throw new Error(`Active file "${activeFile}" is missing.`);
@@ -30933,7 +31138,8 @@ async function exportExactFromStore(format, preferredStem) {
30933
31138
  quality: runQuality,
30934
31139
  paramOverrides,
30935
31140
  assemblyState,
30936
- isNotebook: false
31141
+ isNotebook: false,
31142
+ activeBackend
30937
31143
  });
30938
31144
  triggerDownload(blob, `${stem}.${format}`);
30939
31145
  } finally {
@@ -33624,7 +33830,7 @@ const PHASE_CONFIG = {
33624
33830
  };
33625
33831
  const PHASE_ORDER = ["kernel-init", "evaluating", "serializing"];
33626
33832
  function formatEvaluationBackendLabel(activeBackend, computeTarget) {
33627
- const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : "kernel";
33833
+ const backend = activeBackend === "occt" ? "OCCT" : activeBackend === "manifold" ? "Manifold" : activeBackend === "truck" ? "Truck" : activeBackend === "sdf" ? "SDF" : "kernel";
33628
33834
  return computeTarget === "server" ? "Server OCCT" : `Local ${backend}`;
33629
33835
  }
33630
33836
  function EvaluationIndicator({
@@ -34218,6 +34424,712 @@ function SectionCapPreview({
34218
34424
  if (previewPlanes.length === 0) return null;
34219
34425
  return /* @__PURE__ */ jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment, { children: previewPlanes.map((plane) => /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { geometry: plane.geometry, renderOrder: plane.renderOrder, raycast: () => null, children: /* @__PURE__ */ jsxRuntimeExports.jsx(PreviewCapMaterial, { color: color2, opacity, plane }) }, plane.planeId)) });
34220
34426
  }
34427
+ const DEFAULT_TARGET_LONGEST_AXIS_SAMPLES = 96;
34428
+ const DEFAULT_MAX_VOXELS = 18e4;
34429
+ const DEFAULT_MIN_AXIS_SAMPLES = 8;
34430
+ const BRICK_PROGRAM_OPCODE_THRESHOLD = 768;
34431
+ function compiledSdfProgramFromSceneData(program) {
34432
+ return {
34433
+ opcodes: Uint8Array.from(program.opcodes),
34434
+ argA: Int32Array.from(program.argA),
34435
+ argB: Int32Array.from(program.argB),
34436
+ argC: Int32Array.from(program.argC),
34437
+ constants: Float64Array.from(program.constants),
34438
+ output: program.output,
34439
+ slotCount: program.slotCount
34440
+ };
34441
+ }
34442
+ function shouldUseSdfDistanceBrick(data, opcodeThreshold = BRICK_PROGRAM_OPCODE_THRESHOLD) {
34443
+ return Boolean((data == null ? void 0 : data.program) && data.program.opcodes.length >= opcodeThreshold && finitePositiveBounds(data.bounds));
34444
+ }
34445
+ function chooseSdfDistanceBrickResolution(bounds, options = {}) {
34446
+ const targetLongestAxisSamples = clampInteger(options.targetLongestAxisSamples ?? DEFAULT_TARGET_LONGEST_AXIS_SAMPLES, 4, 256);
34447
+ const maxVoxels = clampInteger(options.maxVoxels ?? DEFAULT_MAX_VOXELS, 512, 16e6);
34448
+ const minAxisSamples = clampInteger(options.minAxisSamples ?? DEFAULT_MIN_AXIS_SAMPLES, 2, 64);
34449
+ const size = boundsSize(bounds);
34450
+ const longest = Math.max(...size);
34451
+ if (!Number.isFinite(longest) || longest <= 0) {
34452
+ return { dims: [minAxisSamples, minAxisSamples, minAxisSamples], cellSize: 1, voxelCount: minAxisSamples ** 3 };
34453
+ }
34454
+ let samples = targetLongestAxisSamples;
34455
+ while (samples >= minAxisSamples) {
34456
+ const cellSize = longest / Math.max(1, samples - 1);
34457
+ const dims = size.map((axis) => Math.max(minAxisSamples, Math.ceil(axis / cellSize) + 1));
34458
+ const voxelCount = dims[0] * dims[1] * dims[2];
34459
+ if (voxelCount <= maxVoxels || samples === minAxisSamples) return { dims, cellSize, voxelCount };
34460
+ samples = Math.max(minAxisSamples, Math.floor(samples * 0.9));
34461
+ }
34462
+ return {
34463
+ dims: [minAxisSamples, minAxisSamples, minAxisSamples],
34464
+ cellSize: longest / Math.max(1, minAxisSamples - 1),
34465
+ voxelCount: minAxisSamples ** 3
34466
+ };
34467
+ }
34468
+ function buildSdfDistanceBrick(data, options = {}) {
34469
+ const startedAt = performance.now();
34470
+ const resolution = chooseSdfDistanceBrickResolution(data.bounds, options);
34471
+ const program = compiledSdfProgramFromSceneData(data.program);
34472
+ const evalSdf = compileSdfProgramEvaluator3(program);
34473
+ const distances = new Float32Array(resolution.voxelCount);
34474
+ const min = [...data.bounds.min];
34475
+ const max2 = [...data.bounds.max];
34476
+ const size = boundsSize(data.bounds);
34477
+ const [nx, ny, nz] = resolution.dims;
34478
+ let offset = 0;
34479
+ for (let z = 0; z < nz; z += 1) {
34480
+ const pz = coordinateAt(min[2], size[2], z, nz);
34481
+ for (let y = 0; y < ny; y += 1) {
34482
+ const py = coordinateAt(min[1], size[1], y, ny);
34483
+ for (let x = 0; x < nx; x += 1) {
34484
+ const px = coordinateAt(min[0], size[0], x, nx);
34485
+ const d = evalSdf(px, py, pz);
34486
+ distances[offset] = Number.isFinite(d) ? d : 1e9;
34487
+ offset += 1;
34488
+ }
34489
+ }
34490
+ }
34491
+ return {
34492
+ ...resolution,
34493
+ min,
34494
+ max: max2,
34495
+ distances,
34496
+ buildMs: performance.now() - startedAt,
34497
+ buildSource: "cpu"
34498
+ };
34499
+ }
34500
+ function finitePositiveBounds(bounds) {
34501
+ const size = boundsSize(bounds);
34502
+ return [...bounds.min, ...bounds.max, ...size].every(Number.isFinite) && size.every((axis) => axis > 0);
34503
+ }
34504
+ function boundsSize(bounds) {
34505
+ return [bounds.max[0] - bounds.min[0], bounds.max[1] - bounds.min[1], bounds.max[2] - bounds.min[2]];
34506
+ }
34507
+ function coordinateAt(min, size, index, count) {
34508
+ if (count <= 1) return min;
34509
+ return min + size * (index / (count - 1));
34510
+ }
34511
+ function clampInteger(value, min, max2) {
34512
+ if (!Number.isFinite(value)) return min;
34513
+ return Math.max(min, Math.min(max2, Math.round(value)));
34514
+ }
34515
+ const DEFAULT_WORKGROUP_SIZE = [4, 4, 4];
34516
+ const BRICK_PARAMS_BYTE_LENGTH = 48;
34517
+ async function buildSdfDistanceBrickWebGpu(data, options = {}) {
34518
+ var _a3, _b2, _c, _d;
34519
+ const gpu = options.gpu ?? currentBrowserGpu();
34520
+ if (!gpu) return null;
34521
+ const adapter = await gpu.requestAdapter();
34522
+ if (!adapter) return null;
34523
+ const startedAt = performance.now();
34524
+ const device = await adapter.requestDevice();
34525
+ const buffers = [];
34526
+ try {
34527
+ const resolution = chooseSdfDistanceBrickResolution(data.bounds, options);
34528
+ const min = [...data.bounds.min];
34529
+ const max2 = [...data.bounds.max];
34530
+ const step = brickStep(data.bounds, resolution.dims);
34531
+ const byteLength2 = resolution.voxelCount * Float32Array.BYTES_PER_ELEMENT;
34532
+ const workgroupSize = options.workgroupSize ?? DEFAULT_WORKGROUP_SIZE;
34533
+ const entryPoint = "sampleSdfBrick";
34534
+ const module = device.createShaderModule({
34535
+ label: "ForgeCAD SDF distance brick compute shader",
34536
+ code: buildSdfProgramBrickComputeWgsl(compiledSdfProgramFromSceneData(data.program), { entryPoint, workgroupSize })
34537
+ });
34538
+ const pipeline = device.createComputePipeline({
34539
+ label: "ForgeCAD SDF distance brick compute pipeline",
34540
+ layout: "auto",
34541
+ compute: { module, entryPoint }
34542
+ });
34543
+ const usage = webGpuUsage();
34544
+ const paramsBuffer = device.createBuffer({
34545
+ label: "ForgeCAD SDF distance brick params",
34546
+ size: BRICK_PARAMS_BYTE_LENGTH,
34547
+ usage: usage.UNIFORM | usage.COPY_DST
34548
+ });
34549
+ const distancesBuffer = device.createBuffer({
34550
+ label: "ForgeCAD SDF distance brick distances",
34551
+ size: byteLength2,
34552
+ usage: usage.STORAGE | usage.COPY_SRC
34553
+ });
34554
+ const readBuffer = device.createBuffer({
34555
+ label: "ForgeCAD SDF distance brick readback",
34556
+ size: byteLength2,
34557
+ usage: usage.MAP_READ | usage.COPY_DST
34558
+ });
34559
+ buffers.push(paramsBuffer, distancesBuffer, readBuffer);
34560
+ device.queue.writeBuffer(paramsBuffer, 0, packSdfDistanceBrickWebGpuParams(min, step, resolution.dims));
34561
+ const bindGroup = device.createBindGroup({
34562
+ label: "ForgeCAD SDF distance brick bind group",
34563
+ layout: pipeline.getBindGroupLayout(0),
34564
+ entries: [
34565
+ { binding: 0, resource: { buffer: paramsBuffer } },
34566
+ { binding: 1, resource: { buffer: distancesBuffer } }
34567
+ ]
34568
+ });
34569
+ const encoder2 = device.createCommandEncoder({ label: "ForgeCAD SDF distance brick command encoder" });
34570
+ const pass = encoder2.beginComputePass({ label: "ForgeCAD SDF distance brick compute pass" });
34571
+ pass.setPipeline(pipeline);
34572
+ pass.setBindGroup(0, bindGroup);
34573
+ pass.dispatchWorkgroups(
34574
+ Math.ceil(resolution.dims[0] / workgroupSize[0]),
34575
+ Math.ceil(resolution.dims[1] / workgroupSize[1]),
34576
+ Math.ceil(resolution.dims[2] / workgroupSize[2])
34577
+ );
34578
+ pass.end();
34579
+ encoder2.copyBufferToBuffer(distancesBuffer, 0, readBuffer, 0, byteLength2);
34580
+ device.queue.submit([encoder2.finish()]);
34581
+ await ((_b2 = (_a3 = device.queue).onSubmittedWorkDone) == null ? void 0 : _b2.call(_a3));
34582
+ await readBuffer.mapAsync(webGpuMapModeRead());
34583
+ const mapped = readBuffer.getMappedRange();
34584
+ const distances = new Float32Array(mapped.slice(0, byteLength2));
34585
+ readBuffer.unmap();
34586
+ return {
34587
+ ...resolution,
34588
+ min,
34589
+ max: max2,
34590
+ distances,
34591
+ buildMs: performance.now() - startedAt,
34592
+ buildSource: "webgpu"
34593
+ };
34594
+ } finally {
34595
+ for (const buffer of buffers) (_c = buffer.destroy) == null ? void 0 : _c.call(buffer);
34596
+ (_d = device.destroy) == null ? void 0 : _d.call(device);
34597
+ }
34598
+ }
34599
+ function isSdfDistanceBrickWebGpuEnabled() {
34600
+ if (typeof window === "undefined") return false;
34601
+ const params = new URLSearchParams(window.location.search);
34602
+ return params.has("sdf-webgpu-bricks") || window.localStorage.getItem("fc-sdf-webgpu-bricks") === "1";
34603
+ }
34604
+ function packSdfDistanceBrickWebGpuParams(origin, step, dims) {
34605
+ const buffer = new ArrayBuffer(BRICK_PARAMS_BYTE_LENGTH);
34606
+ const floats = new Float32Array(buffer, 0, 8);
34607
+ floats[0] = origin[0];
34608
+ floats[1] = origin[1];
34609
+ floats[2] = origin[2];
34610
+ floats[3] = 0;
34611
+ floats[4] = step[0];
34612
+ floats[5] = step[1];
34613
+ floats[6] = step[2];
34614
+ floats[7] = 0;
34615
+ const uints = new Uint32Array(buffer, 32, 4);
34616
+ uints[0] = dims[0];
34617
+ uints[1] = dims[1];
34618
+ uints[2] = dims[2];
34619
+ uints[3] = 0;
34620
+ return buffer;
34621
+ }
34622
+ function brickStep(bounds, dims) {
34623
+ return [
34624
+ axisStep(bounds.min[0], bounds.max[0], dims[0]),
34625
+ axisStep(bounds.min[1], bounds.max[1], dims[1]),
34626
+ axisStep(bounds.min[2], bounds.max[2], dims[2])
34627
+ ];
34628
+ }
34629
+ function axisStep(min, max2, count) {
34630
+ if (count <= 1) return 0;
34631
+ return (max2 - min) / (count - 1);
34632
+ }
34633
+ function currentBrowserGpu() {
34634
+ const nav = globalThis.navigator;
34635
+ return (nav == null ? void 0 : nav.gpu) ?? null;
34636
+ }
34637
+ function webGpuUsage() {
34638
+ return globalThis.GPUBufferUsage ?? {
34639
+ MAP_READ: 1,
34640
+ COPY_SRC: 4,
34641
+ COPY_DST: 8,
34642
+ UNIFORM: 64,
34643
+ STORAGE: 128
34644
+ };
34645
+ }
34646
+ function webGpuMapModeRead() {
34647
+ var _a3;
34648
+ return ((_a3 = globalThis.GPUMapMode) == null ? void 0 : _a3.READ) ?? 1;
34649
+ }
34650
+ const MAX_CLIP_PLANES$1 = 8;
34651
+ const SDF_BRICK_VERTEX_SHADER = `precision highp float;
34652
+
34653
+ out vec3 vLocalPosition;
34654
+
34655
+ void main() {
34656
+ vLocalPosition = position;
34657
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
34658
+ }
34659
+ `;
34660
+ const SDF_BRICK_FRAGMENT_SHADER = `precision highp float;
34661
+ precision highp sampler3D;
34662
+
34663
+ in vec3 vLocalPosition;
34664
+ out vec4 outColor;
34665
+
34666
+ ${SDF_LINEAR_OUTPUT_COLOR_GLSL}
34667
+
34668
+ uniform sampler3D uDistanceTexture;
34669
+ uniform mat4 uCameraMatrixWorld;
34670
+ uniform mat4 uLocalToWorld;
34671
+ uniform mat4 uWorldToLocal;
34672
+ uniform mat4 uViewProjectionMatrix;
34673
+ uniform vec3 uBrickMin;
34674
+ uniform vec3 uBrickMax;
34675
+ uniform vec3 uColor;
34676
+ uniform vec3 uHoverColor;
34677
+ uniform float uAlpha;
34678
+ uniform float uHoverIntensity;
34679
+ uniform float uRayStep;
34680
+ uniform float uIsoEps;
34681
+ uniform float uNormalStep;
34682
+ uniform float uIsOrthographic;
34683
+ uniform int uClipPlaneCount;
34684
+ uniform vec4 uClipPlanes[8];
34685
+
34686
+ const int MAX_STEPS = 420;
34687
+ const int REFINE_STEPS = 7;
34688
+
34689
+ bool intersectAabb(vec3 ro, vec3 rd, vec3 bmin, vec3 bmax, out float tNear, out float tFar) {
34690
+ vec3 inv = 1.0 / rd;
34691
+ vec3 t0 = (bmin - ro) * inv;
34692
+ vec3 t1 = (bmax - ro) * inv;
34693
+ vec3 tmin = min(t0, t1);
34694
+ vec3 tmax = max(t0, t1);
34695
+ tNear = max(max(tmin.x, tmin.y), tmin.z);
34696
+ tFar = min(min(tmax.x, tmax.y), tmax.z);
34697
+ return tFar >= max(tNear, 0.0);
34698
+ }
34699
+
34700
+ float brickSdf(vec3 p) {
34701
+ vec3 uv = clamp((p - uBrickMin) / max(uBrickMax - uBrickMin, vec3(1e-6)), vec3(0.0), vec3(1.0));
34702
+ return texture(uDistanceTexture, uv).r;
34703
+ }
34704
+
34705
+ float clippedBrickSdf(vec3 p) {
34706
+ float d = brickSdf(p);
34707
+ for (int i = 0; i < 8; i++) {
34708
+ if (i >= uClipPlaneCount) break;
34709
+ vec4 plane = uClipPlanes[i];
34710
+ d = max(d, dot(plane.xyz, p) + plane.w);
34711
+ }
34712
+ return d;
34713
+ }
34714
+
34715
+ bool crossedSurface(float a, float b) {
34716
+ return (a < 0.0 && b > 0.0) || (a > 0.0 && b < 0.0);
34717
+ }
34718
+
34719
+ float refineSurface(vec3 ro, vec3 rd, float lo, float hi) {
34720
+ float dLo = clippedBrickSdf(ro + rd * lo);
34721
+ for (int i = 0; i < REFINE_STEPS; i++) {
34722
+ float mid = 0.5 * (lo + hi);
34723
+ float dMid = clippedBrickSdf(ro + rd * mid);
34724
+ if (abs(dMid) < uIsoEps) return mid;
34725
+ if (crossedSurface(dLo, dMid)) {
34726
+ hi = mid;
34727
+ } else {
34728
+ lo = mid;
34729
+ dLo = dMid;
34730
+ }
34731
+ }
34732
+ return 0.5 * (lo + hi);
34733
+ }
34734
+
34735
+ vec3 estimateNormal(vec3 p) {
34736
+ vec2 e = vec2(uNormalStep, 0.0);
34737
+ return normalize(vec3(
34738
+ brickSdf(p + e.xyy) - brickSdf(p - e.xyy),
34739
+ brickSdf(p + e.yxy) - brickSdf(p - e.yxy),
34740
+ brickSdf(p + e.yyx) - brickSdf(p - e.yyx)
34741
+ ));
34742
+ }
34743
+
34744
+ void writeFragmentDepth(vec3 pLocal) {
34745
+ vec4 world = uLocalToWorld * vec4(pLocal, 1.0);
34746
+ vec4 clip = uViewProjectionMatrix * world;
34747
+ float ndcDepth = clip.z / clip.w;
34748
+ gl_FragDepth = ndcDepth * 0.5 + 0.5;
34749
+ }
34750
+
34751
+ void main() {
34752
+ float sceneDiag = max(length(uBrickMax - uBrickMin), 1.0);
34753
+ vec3 roWorld = uCameraMatrixWorld[3].xyz;
34754
+ vec3 ro = (uWorldToLocal * vec4(roWorld, 1.0)).xyz;
34755
+ vec3 rd = normalize(vLocalPosition - ro);
34756
+ if (uIsOrthographic > 0.5) {
34757
+ vec3 rdWorld = normalize((uCameraMatrixWorld * vec4(0.0, 0.0, -1.0, 0.0)).xyz);
34758
+ rd = normalize((uWorldToLocal * vec4(rdWorld, 0.0)).xyz);
34759
+ ro = vLocalPosition - rd * sceneDiag * 2.0;
34760
+ }
34761
+
34762
+ float tNear = 0.0;
34763
+ float tFar = 0.0;
34764
+ if (!intersectAabb(ro, rd, uBrickMin, uBrickMax, tNear, tFar)) discard;
34765
+
34766
+ float t = max(tNear, 0.0);
34767
+ float prevT = t;
34768
+ float prevD = clippedBrickSdf(ro + rd * t);
34769
+ bool hit = prevD <= 0.0 && abs(prevD) < uIsoEps;
34770
+
34771
+ for (int i = 0; i < MAX_STEPS; i++) {
34772
+ if (hit) break;
34773
+ t += clamp(abs(prevD) * 0.8, uRayStep, uRayStep * 4.0);
34774
+ if (t > tFar) break;
34775
+ float d = clippedBrickSdf(ro + rd * t);
34776
+ float absD = abs(d);
34777
+ if (d <= 0.0 && absD < uIsoEps) {
34778
+ hit = true;
34779
+ break;
34780
+ }
34781
+ if (crossedSurface(prevD, d)) {
34782
+ t = refineSurface(ro, rd, prevT, t);
34783
+ hit = true;
34784
+ break;
34785
+ }
34786
+ prevT = t;
34787
+ prevD = d;
34788
+ }
34789
+
34790
+ if (!hit) discard;
34791
+
34792
+ vec3 p = ro + rd * t;
34793
+ writeFragmentDepth(p);
34794
+ vec3 pWorld = (uLocalToWorld * vec4(p, 1.0)).xyz;
34795
+ vec3 normal = normalize(mat3(transpose(uWorldToLocal)) * estimateNormal(p));
34796
+ vec3 viewDir = normalize(roWorld - pWorld);
34797
+ vec3 keyDir = normalize(vec3(0.45, 0.62, 0.64));
34798
+ vec3 fillDir = normalize(vec3(-0.68, -0.18, 0.42));
34799
+ float diffuse = max(dot(normal, keyDir), 0.0) * 0.72 + max(dot(normal, fillDir), 0.0) * 0.22 + 0.16;
34800
+ float fresnel = pow(1.0 - max(dot(normal, viewDir), 0.0), 3.0);
34801
+ vec3 base = mix(uColor, uHoverColor, uHoverIntensity);
34802
+ vec3 shaded = base * diffuse + base * fresnel * 0.18;
34803
+ outColor = forgeLinearToOutputColor(shaded, uAlpha);
34804
+ }
34805
+ `;
34806
+ function colorToRgb$1(color2, fallback) {
34807
+ const resolved = new Color(color2 ?? fallback);
34808
+ return [resolved.r, resolved.g, resolved.b];
34809
+ }
34810
+ function createProxyGeometry$1(data) {
34811
+ const { min, max: max2 } = data.bounds;
34812
+ const size = [max2[0] - min[0], max2[1] - min[1], max2[2] - min[2]];
34813
+ if (!size.every((value) => Number.isFinite(value) && value > 0)) return null;
34814
+ const geometry = new BoxGeometry(size[0], size[1], size[2]);
34815
+ geometry.translate((min[0] + max2[0]) / 2, (min[1] + max2[1]) / 2, (min[2] + max2[2]) / 2);
34816
+ return geometry;
34817
+ }
34818
+ function createClipPlaneUniforms$1() {
34819
+ return Array.from({ length: MAX_CLIP_PLANES$1 }, () => new Vector4(0, 0, 0, 0));
34820
+ }
34821
+ function createDistanceTexture(brick) {
34822
+ const texture = new Data3DTexture(brick.distances, brick.dims[0], brick.dims[1], brick.dims[2]);
34823
+ texture.format = RedFormat;
34824
+ texture.type = FloatType;
34825
+ texture.minFilter = LinearFilter;
34826
+ texture.magFilter = LinearFilter;
34827
+ texture.wrapS = ClampToEdgeWrapping;
34828
+ texture.wrapT = ClampToEdgeWrapping;
34829
+ texture.wrapR = ClampToEdgeWrapping;
34830
+ texture.unpackAlignment = 1;
34831
+ texture.needsUpdate = true;
34832
+ return texture;
34833
+ }
34834
+ async function buildViewportDistanceBrick(sdf) {
34835
+ if (!shouldUseSdfDistanceBrick(sdf)) return null;
34836
+ if (isSdfDistanceBrickWebGpuEnabled()) {
34837
+ const webGpuBrick = await buildSdfDistanceBrickWebGpu(sdf);
34838
+ if (webGpuBrick) return webGpuBrick;
34839
+ }
34840
+ return buildSdfDistanceBrick(sdf);
34841
+ }
34842
+ function SdfDistanceBrickObject({
34843
+ sdf,
34844
+ settings,
34845
+ matrix,
34846
+ materialProps,
34847
+ isHovered,
34848
+ clippingPlanes = [],
34849
+ onPointerEnter,
34850
+ onPointerMove,
34851
+ onPointerLeave,
34852
+ onClick,
34853
+ onDoubleClick,
34854
+ onContextMenu
34855
+ }) {
34856
+ const meshRef = reactExports.useRef(null);
34857
+ const materialRef = reactExports.useRef(null);
34858
+ const { camera } = useThree();
34859
+ const viewProjection = reactExports.useMemo(() => new Matrix4(), []);
34860
+ const worldToLocal = reactExports.useMemo(() => new Matrix4(), []);
34861
+ const localClipPlane = reactExports.useMemo(() => new Plane(), []);
34862
+ const [brick, setBrick] = reactExports.useState(null);
34863
+ const [brickError, setBrickError] = reactExports.useState(null);
34864
+ reactExports.useEffect(() => {
34865
+ let cancelled = false;
34866
+ setBrick(null);
34867
+ setBrickError(null);
34868
+ buildViewportDistanceBrick(sdf).then((nextBrick) => {
34869
+ if (!cancelled) setBrick(nextBrick);
34870
+ }).catch((error) => {
34871
+ const nextError = error instanceof Error ? error : new Error(String(error));
34872
+ console.error("ForgeCAD SDF distance brick build failed.", nextError);
34873
+ if (!cancelled) setBrickError(nextError);
34874
+ });
34875
+ return () => {
34876
+ cancelled = true;
34877
+ };
34878
+ }, [sdf]);
34879
+ const geometry = reactExports.useMemo(() => createProxyGeometry$1(sdf), [sdf]);
34880
+ const texture = reactExports.useMemo(() => brick ? createDistanceTexture(brick) : null, [brick]);
34881
+ const opacity = Math.min(settings.opacity, (materialProps == null ? void 0 : materialProps.opacity) ?? 1);
34882
+ const color2 = reactExports.useMemo(() => colorToRgb$1(settings.color ?? sdf.colorHex, "#5b9bd5"), [settings.color, sdf.colorHex]);
34883
+ const uniforms = reactExports.useMemo(
34884
+ () => ({
34885
+ uDistanceTexture: { value: texture },
34886
+ uCameraMatrixWorld: { value: new Matrix4() },
34887
+ uLocalToWorld: { value: new Matrix4() },
34888
+ uWorldToLocal: { value: new Matrix4() },
34889
+ uViewProjectionMatrix: { value: new Matrix4() },
34890
+ uBrickMin: { value: new Vector3((brick == null ? void 0 : brick.min[0]) ?? 0, (brick == null ? void 0 : brick.min[1]) ?? 0, (brick == null ? void 0 : brick.min[2]) ?? 0) },
34891
+ uBrickMax: { value: new Vector3((brick == null ? void 0 : brick.max[0]) ?? 1, (brick == null ? void 0 : brick.max[1]) ?? 1, (brick == null ? void 0 : brick.max[2]) ?? 1) },
34892
+ uColor: { value: new Vector3(...color2) },
34893
+ uHoverColor: { value: new Color(settings.color) },
34894
+ uAlpha: { value: opacity },
34895
+ uHoverIntensity: { value: 0 },
34896
+ uRayStep: { value: Math.max(0.05, ((brick == null ? void 0 : brick.cellSize) ?? 1) * 0.65) },
34897
+ uIsoEps: { value: Math.max(0.05, ((brick == null ? void 0 : brick.cellSize) ?? 1) * 0.55) },
34898
+ uNormalStep: { value: Math.max(0.05, (brick == null ? void 0 : brick.cellSize) ?? 1) },
34899
+ uIsOrthographic: { value: 0 },
34900
+ uClipPlaneCount: { value: 0 },
34901
+ uClipPlanes: { value: createClipPlaneUniforms$1() }
34902
+ }),
34903
+ [brick, color2, opacity, settings.color, texture]
34904
+ );
34905
+ useFrame(() => {
34906
+ const material = materialRef.current;
34907
+ const mesh = meshRef.current;
34908
+ if (!material || !mesh) return;
34909
+ mesh.updateMatrixWorld();
34910
+ camera.updateMatrixWorld();
34911
+ viewProjection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
34912
+ worldToLocal.copy(mesh.matrixWorld).invert();
34913
+ material.uniforms.uCameraMatrixWorld.value.copy(camera.matrixWorld);
34914
+ material.uniforms.uLocalToWorld.value.copy(mesh.matrixWorld);
34915
+ material.uniforms.uWorldToLocal.value.copy(worldToLocal);
34916
+ material.uniforms.uViewProjectionMatrix.value.copy(viewProjection);
34917
+ material.uniforms.uIsOrthographic.value = camera.isOrthographicCamera ? 1 : 0;
34918
+ material.uniforms.uHoverColor.value.set(settings.color);
34919
+ material.uniforms.uHoverIntensity.value = isHovered ? 0.3 : 0;
34920
+ material.uniforms.uClipPlaneCount.value = Math.min(clippingPlanes.length, MAX_CLIP_PLANES$1);
34921
+ const clipUniforms = material.uniforms.uClipPlanes.value;
34922
+ for (let i = 0; i < MAX_CLIP_PLANES$1; i += 1) {
34923
+ const source = clippingPlanes[i];
34924
+ if (!source) {
34925
+ clipUniforms[i].set(0, 0, 0, 0);
34926
+ continue;
34927
+ }
34928
+ localClipPlane.copy(source).applyMatrix4(worldToLocal);
34929
+ clipUniforms[i].set(localClipPlane.normal.x, localClipPlane.normal.y, localClipPlane.normal.z, localClipPlane.constant);
34930
+ }
34931
+ });
34932
+ reactExports.useEffect(() => {
34933
+ return () => {
34934
+ geometry == null ? void 0 : geometry.dispose();
34935
+ };
34936
+ }, [geometry]);
34937
+ reactExports.useEffect(() => {
34938
+ return () => {
34939
+ texture == null ? void 0 : texture.dispose();
34940
+ };
34941
+ }, [texture]);
34942
+ if (brickError || !brick || !geometry || !texture) return null;
34943
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
34944
+ "group",
34945
+ {
34946
+ matrixAutoUpdate: false,
34947
+ matrix,
34948
+ onPointerEnter,
34949
+ onPointerMove,
34950
+ onPointerLeave,
34951
+ onClick,
34952
+ onDoubleClick,
34953
+ onContextMenu,
34954
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34955
+ "mesh",
34956
+ {
34957
+ ref: meshRef,
34958
+ geometry,
34959
+ userData: {
34960
+ forgeMesh: true,
34961
+ forgeSdfDirect: true,
34962
+ forgeSdfRenderer: "distance-brick",
34963
+ forgeSdfDistanceBrick: true,
34964
+ forgeSdfDistanceBrickBuildSource: brick.buildSource,
34965
+ forgeSdfDistanceBrickBuildMs: brick.buildMs,
34966
+ forgeSdfDistanceBrickVoxels: brick.voxelCount
34967
+ },
34968
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx(
34969
+ "shaderMaterial",
34970
+ {
34971
+ ref: materialRef,
34972
+ glslVersion: GLSL3,
34973
+ vertexShader: SDF_BRICK_VERTEX_SHADER,
34974
+ fragmentShader: SDF_BRICK_FRAGMENT_SHADER,
34975
+ uniforms,
34976
+ side: DoubleSide,
34977
+ transparent: opacity < 1 || ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) > 0,
34978
+ depthWrite: opacity >= 1 && ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) <= 0,
34979
+ toneMapped: false
34980
+ }
34981
+ )
34982
+ }
34983
+ )
34984
+ }
34985
+ );
34986
+ }
34987
+ const MAX_CLIP_PLANES = 8;
34988
+ function colorToRgb(color2, fallback) {
34989
+ const resolved = new Color(color2 ?? fallback);
34990
+ return [resolved.r, resolved.g, resolved.b];
34991
+ }
34992
+ function sdfLeaf(data, settings, materialProps) {
34993
+ const opacity = Math.min(settings.opacity, (materialProps == null ? void 0 : materialProps.opacity) ?? 1);
34994
+ return {
34995
+ node: data.node,
34996
+ ...data.program ? { program: data.program } : {},
34997
+ color: colorToRgb(settings.color ?? data.colorHex, "#5b9bd5"),
34998
+ alpha: opacity,
34999
+ emissive: colorToRgb(materialProps == null ? void 0 : materialProps.emissive, "#000000"),
35000
+ emissiveIntensity: (materialProps == null ? void 0 : materialProps.emissiveIntensity) ?? 0,
35001
+ metalness: (materialProps == null ? void 0 : materialProps.metalness) ?? 0.05,
35002
+ roughness: (materialProps == null ? void 0 : materialProps.roughness) ?? 0.35,
35003
+ clearcoat: (materialProps == null ? void 0 : materialProps.clearcoat) ?? 0.1,
35004
+ clearcoatRoughness: (materialProps == null ? void 0 : materialProps.clearcoatRoughness) ?? 0.4,
35005
+ transmission: (materialProps == null ? void 0 : materialProps.transmission) ?? 0,
35006
+ reflectivity: (materialProps == null ? void 0 : materialProps.reflectivity) ?? 0.5
35007
+ };
35008
+ }
35009
+ function canRenderSdfDirectly(data) {
35010
+ return Boolean(data && data.preview.mode === "raymarch");
35011
+ }
35012
+ function createProxyGeometry(data) {
35013
+ const { min, max: max2 } = data.bounds;
35014
+ const size = [max2[0] - min[0], max2[1] - min[1], max2[2] - min[2]];
35015
+ if (!size.every((value) => Number.isFinite(value) && value > 0)) return null;
35016
+ const geometry = new BoxGeometry(size[0], size[1], size[2]);
35017
+ geometry.translate((min[0] + max2[0]) / 2, (min[1] + max2[1]) / 2, (min[2] + max2[2]) / 2);
35018
+ return geometry;
35019
+ }
35020
+ function createClipPlaneUniforms() {
35021
+ return Array.from({ length: MAX_CLIP_PLANES }, () => new Vector4(0, 0, 0, 0));
35022
+ }
35023
+ function SdfDirectObject(props) {
35024
+ const gl = useThree((state2) => state2.gl);
35025
+ if (shouldUseSdfDistanceBrick(props.sdf) && gl.capabilities.isWebGL2) {
35026
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(SdfDistanceBrickObject, { ...props });
35027
+ }
35028
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(SdfRaymarchObject, { ...props });
35029
+ }
35030
+ function SdfRaymarchObject({
35031
+ sdf,
35032
+ settings,
35033
+ matrix,
35034
+ materialProps,
35035
+ isHovered,
35036
+ clippingPlanes = [],
35037
+ onPointerEnter,
35038
+ onPointerMove,
35039
+ onPointerLeave,
35040
+ onClick,
35041
+ onDoubleClick,
35042
+ onContextMenu
35043
+ }) {
35044
+ const meshRef = reactExports.useRef(null);
35045
+ const materialRef = reactExports.useRef(null);
35046
+ const { camera } = useThree();
35047
+ const viewProjection = reactExports.useMemo(() => new Matrix4(), []);
35048
+ const worldToLocal = reactExports.useMemo(() => new Matrix4(), []);
35049
+ const localClipPlane = reactExports.useMemo(() => new Plane(), []);
35050
+ const geometry = reactExports.useMemo(() => createProxyGeometry(sdf), [sdf]);
35051
+ const fragmentShader2 = reactExports.useMemo(
35052
+ () => buildSdfRaymarchFragmentShader([sdfLeaf(sdf, settings, materialProps)]),
35053
+ [materialProps, settings, sdf]
35054
+ );
35055
+ const uniforms = reactExports.useMemo(
35056
+ () => ({
35057
+ uCameraMatrixWorld: { value: new Matrix4() },
35058
+ uLocalToWorld: { value: new Matrix4() },
35059
+ uWorldToLocal: { value: new Matrix4() },
35060
+ uViewProjectionMatrix: { value: new Matrix4() },
35061
+ uSceneMin: { value: new Vector3(sdf.bounds.min[0], sdf.bounds.min[1], sdf.bounds.min[2]) },
35062
+ uSceneMax: { value: new Vector3(sdf.bounds.max[0], sdf.bounds.max[1], sdf.bounds.max[2]) },
35063
+ uClipPlaneCount: { value: 0 },
35064
+ uClipPlanes: { value: createClipPlaneUniforms() },
35065
+ uHoverLeafIndex: { value: -1 },
35066
+ uHoverColor: { value: new Color(settings.color) },
35067
+ uHoverIntensity: { value: 0 },
35068
+ uIsOrthographic: { value: 0 }
35069
+ }),
35070
+ [sdf.bounds.max, sdf.bounds.min, settings.color]
35071
+ );
35072
+ useFrame(() => {
35073
+ const material = materialRef.current;
35074
+ const mesh = meshRef.current;
35075
+ if (!material || !mesh) return;
35076
+ mesh.updateMatrixWorld();
35077
+ camera.updateMatrixWorld();
35078
+ viewProjection.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
35079
+ worldToLocal.copy(mesh.matrixWorld).invert();
35080
+ material.uniforms.uCameraMatrixWorld.value.copy(camera.matrixWorld);
35081
+ material.uniforms.uLocalToWorld.value.copy(mesh.matrixWorld);
35082
+ material.uniforms.uWorldToLocal.value.copy(worldToLocal);
35083
+ material.uniforms.uViewProjectionMatrix.value.copy(viewProjection);
35084
+ material.uniforms.uIsOrthographic.value = camera.isOrthographicCamera ? 1 : 0;
35085
+ material.uniforms.uHoverLeafIndex.value = isHovered ? 0 : -1;
35086
+ material.uniforms.uHoverColor.value.set(settings.color);
35087
+ material.uniforms.uHoverIntensity.value = isHovered ? 0.3 : 0;
35088
+ material.uniforms.uClipPlaneCount.value = Math.min(clippingPlanes.length, MAX_CLIP_PLANES);
35089
+ const clipUniforms = material.uniforms.uClipPlanes.value;
35090
+ for (let i = 0; i < MAX_CLIP_PLANES; i += 1) {
35091
+ const source = clippingPlanes[i];
35092
+ if (!source) {
35093
+ clipUniforms[i].set(0, 0, 0, 0);
35094
+ continue;
35095
+ }
35096
+ localClipPlane.copy(source).applyMatrix4(worldToLocal);
35097
+ clipUniforms[i].set(localClipPlane.normal.x, localClipPlane.normal.y, localClipPlane.normal.z, localClipPlane.constant);
35098
+ }
35099
+ });
35100
+ reactExports.useEffect(() => {
35101
+ return () => geometry == null ? void 0 : geometry.dispose();
35102
+ }, [geometry]);
35103
+ if (!geometry) return null;
35104
+ const opacity = Math.min(settings.opacity, (materialProps == null ? void 0 : materialProps.opacity) ?? 1);
35105
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
35106
+ "group",
35107
+ {
35108
+ matrixAutoUpdate: false,
35109
+ matrix,
35110
+ onPointerEnter,
35111
+ onPointerMove,
35112
+ onPointerLeave,
35113
+ onClick,
35114
+ onDoubleClick,
35115
+ onContextMenu,
35116
+ children: /* @__PURE__ */ jsxRuntimeExports.jsx("mesh", { ref: meshRef, geometry, userData: { forgeMesh: true, forgeSdfDirect: true, forgeSdfRenderer: "raymarch" }, children: /* @__PURE__ */ jsxRuntimeExports.jsx(
35117
+ "shaderMaterial",
35118
+ {
35119
+ ref: materialRef,
35120
+ vertexShader: SDF_RAYMARCH_PROXY_VERTEX_SHADER,
35121
+ fragmentShader: fragmentShader2,
35122
+ uniforms,
35123
+ side: DoubleSide,
35124
+ transparent: opacity < 1 || ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) > 0,
35125
+ depthWrite: opacity >= 1 && ((materialProps == null ? void 0 : materialProps.transmission) ?? 0) <= 0,
35126
+ toneMapped: false,
35127
+ extensions: { fragDepth: true }
35128
+ }
35129
+ ) })
35130
+ }
35131
+ );
35132
+ }
34221
35133
  class ScanProxyWorkerClient {
34222
35134
  constructor(workerFactory = () => new Worker(new URL(
34223
35135
  /* @vite-ignore */
@@ -34853,7 +35765,17 @@ function ForgeObject({
34853
35765
  onContextMenu
34854
35766
  }) {
34855
35767
  var _a3, _b2, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
35768
+ const wantsDirectSdf = Boolean(
35769
+ canRenderSdfDirectly(obj.sdf) && inspectChannel === "none" && renderStyle !== "scan" && renderStyle !== "matrix" && renderMode !== "wireframe" && !debugHighlightColor
35770
+ );
34856
35771
  const { solidGeo, edgesGeo, hasSmoothNormals } = reactExports.useMemo(() => {
35772
+ if (wantsDirectSdf) {
35773
+ return {
35774
+ solidGeo: null,
35775
+ edgesGeo: null,
35776
+ hasSmoothNormals: false
35777
+ };
35778
+ }
34857
35779
  if (!obj.shape) {
34858
35780
  return {
34859
35781
  solidGeo: null,
@@ -34875,7 +35797,7 @@ function ForgeObject({
34875
35797
  hasSmoothNormals: false
34876
35798
  };
34877
35799
  }
34878
- }, [obj.shape]);
35800
+ }, [obj.shape, wantsDirectSdf]);
34879
35801
  const isScalarInspect = inspectChannel === "thickness" || inspectChannel === "roughness";
34880
35802
  const isScalarScan = isScalarInspect && inspectDisplayMode === "scan";
34881
35803
  const inspectPointGeo = reactExports.useMemo(() => {
@@ -34934,7 +35856,7 @@ function ForgeObject({
34934
35856
  Object.values(scanAnalysisGeometries ?? {}).forEach((geometry) => geometry == null ? void 0 : geometry.dispose());
34935
35857
  };
34936
35858
  }, [scanAnalysisGeometries]);
34937
- if (!solidGeo || !settings.visible) return null;
35859
+ if (!settings.visible) return null;
34938
35860
  const effectiveRenderMode = isInteracting && renderMode === "overlay" ? "solid" : renderMode;
34939
35861
  const renderStylePreset = getRenderStylePreset(renderStyle);
34940
35862
  const materialDefaults = renderStylePreset.material;
@@ -34954,7 +35876,6 @@ function ForgeObject({
34954
35876
  const showWire = !isInspecting && effectiveRenderMode === "wireframe";
34955
35877
  const effectiveClippingPlanes = clippingPlanes ?? EMPTY_CLIPPING_PLANES$1;
34956
35878
  const effectiveSectionPlanes = sectionPlanes ?? EMPTY_SECTION_PLANES$1;
34957
- const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
34958
35879
  const hasInspectMeshColors = inspectMeshColorGeo !== null;
34959
35880
  const showFloatingContext = showSolid && inspectChannel === "floating";
34960
35881
  const showFloatingObject = inspectChannel !== "floating" || hasInspectMeshColors || inspectColor !== "#000000";
@@ -34976,6 +35897,27 @@ function ForgeObject({
34976
35897
  const showSectionCapPreview = Boolean(
34977
35898
  isInteracting && showSolid && effectiveRenderMode !== "wireframe" && effectiveSectionPlanes.length > 0
34978
35899
  );
35900
+ if (wantsDirectSdf && obj.sdf) {
35901
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
35902
+ SdfDirectObject,
35903
+ {
35904
+ sdf: obj.sdf,
35905
+ settings,
35906
+ matrix,
35907
+ materialProps: obj.materialProps,
35908
+ isHovered,
35909
+ clippingPlanes: effectiveClippingPlanes,
35910
+ onPointerEnter,
35911
+ onPointerMove,
35912
+ onPointerLeave,
35913
+ onClick,
35914
+ onDoubleClick,
35915
+ onContextMenu
35916
+ }
35917
+ );
35918
+ }
35919
+ if (!solidGeo) return null;
35920
+ const inspectSolidGeo = inspectMeshColorGeo ?? solidGeo;
34979
35921
  return /* @__PURE__ */ jsxRuntimeExports.jsxs(
34980
35922
  "group",
34981
35923
  {
@@ -37121,6 +38063,7 @@ function MeasureInfoPanel() {
37121
38063
  }
37122
38064
  return null;
37123
38065
  }
38066
+ const MAX_VIEWPORT_PERFORMANCE_SAMPLES = 240;
37124
38067
  const getProgramCount = (gl) => {
37125
38068
  const info = gl.info;
37126
38069
  return Array.isArray(info.programs) ? info.programs.length : 0;
@@ -37161,6 +38104,26 @@ function collectScanProxyPerformanceInfo(scene) {
37161
38104
  performanceReplacementCount
37162
38105
  } : null;
37163
38106
  }
38107
+ function collectSdfDistanceBrickPerformanceInfo(scene) {
38108
+ let count = 0;
38109
+ let voxels = 0;
38110
+ let buildMs = 0;
38111
+ let cpuCount = 0;
38112
+ let webGpuCount = 0;
38113
+ scene.traverse((object) => {
38114
+ const userData = object.userData;
38115
+ if (!userData.forgeSdfDistanceBrick) return;
38116
+ count += 1;
38117
+ voxels += userData.forgeSdfDistanceBrickVoxels ?? 0;
38118
+ buildMs += userData.forgeSdfDistanceBrickBuildMs ?? 0;
38119
+ if (userData.forgeSdfDistanceBrickBuildSource === "webgpu") {
38120
+ webGpuCount += 1;
38121
+ } else {
38122
+ cpuCount += 1;
38123
+ }
38124
+ });
38125
+ return count > 0 ? { count, voxels, buildMs, cpuCount, webGpuCount } : null;
38126
+ }
37164
38127
  const TREND_WINDOW = 20;
37165
38128
  class TrendTracker {
37166
38129
  constructor(size) {
@@ -37212,6 +38175,88 @@ const formatWorkerStatus = (message) => {
37212
38175
  if (/timed out/i.test(message)) return "restarted after timeout";
37213
38176
  return "restarted after worker error";
37214
38177
  };
38178
+ function summarizeViewportPerformanceSamples(samples) {
38179
+ var _a3, _b2, _c, _d;
38180
+ if (samples.length === 0) {
38181
+ return {
38182
+ samples: 0,
38183
+ latestFps: null,
38184
+ averageFps: null,
38185
+ minFps: null,
38186
+ p10Fps: null,
38187
+ averageFrameTimeMs: null,
38188
+ latestDrawCalls: null,
38189
+ latestRenderTriangles: null,
38190
+ latestModelTriangles: null,
38191
+ latestProgramCount: null,
38192
+ latestSdfDistanceBrickCount: null,
38193
+ latestSdfDistanceBrickVoxels: null,
38194
+ latestSdfDistanceBrickCpuCount: null,
38195
+ latestSdfDistanceBrickWebGpuCount: null
38196
+ };
38197
+ }
38198
+ const latest = samples[samples.length - 1];
38199
+ const fpsValues = samples.map((sample) => sample.fps).sort((a2, b2) => a2 - b2);
38200
+ const fpsTotal = samples.reduce((sum, sample) => sum + sample.fps, 0);
38201
+ const frameTimeTotal = samples.reduce((sum, sample) => sum + sample.frameTimeMs, 0);
38202
+ const p10Index = Math.floor((fpsValues.length - 1) * 0.1);
38203
+ return {
38204
+ samples: samples.length,
38205
+ latestFps: latest.fps,
38206
+ averageFps: fpsTotal / samples.length,
38207
+ minFps: fpsValues[0],
38208
+ p10Fps: fpsValues[p10Index],
38209
+ averageFrameTimeMs: frameTimeTotal / samples.length,
38210
+ latestDrawCalls: latest.drawCalls,
38211
+ latestRenderTriangles: latest.renderTriangles,
38212
+ latestModelTriangles: latest.modelTriangles,
38213
+ latestProgramCount: latest.programCount,
38214
+ latestSdfDistanceBrickCount: ((_a3 = latest.sdfDistanceBricks) == null ? void 0 : _a3.count) ?? 0,
38215
+ latestSdfDistanceBrickVoxels: ((_b2 = latest.sdfDistanceBricks) == null ? void 0 : _b2.voxels) ?? 0,
38216
+ latestSdfDistanceBrickCpuCount: ((_c = latest.sdfDistanceBricks) == null ? void 0 : _c.cpuCount) ?? 0,
38217
+ latestSdfDistanceBrickWebGpuCount: ((_d = latest.sdfDistanceBricks) == null ? void 0 : _d.webGpuCount) ?? 0
38218
+ };
38219
+ }
38220
+ function getViewportPerformanceProbe() {
38221
+ if (typeof window === "undefined") return null;
38222
+ if (!window.__forgeViewportPerformance) {
38223
+ window.__forgeViewportPerformance = {
38224
+ latest: null,
38225
+ samples: [],
38226
+ reset() {
38227
+ this.latest = null;
38228
+ this.samples = [];
38229
+ },
38230
+ summary(sampleLimit) {
38231
+ const selected = typeof sampleLimit === "number" && Number.isFinite(sampleLimit) && sampleLimit > 0 ? this.samples.slice(-Math.round(sampleLimit)) : this.samples;
38232
+ return summarizeViewportPerformanceSamples(selected);
38233
+ }
38234
+ };
38235
+ }
38236
+ return window.__forgeViewportPerformance;
38237
+ }
38238
+ function publishViewportPerformanceSample(stats) {
38239
+ const probe = getViewportPerformanceProbe();
38240
+ if (!probe) return;
38241
+ if (!stats) {
38242
+ probe.latest = null;
38243
+ return;
38244
+ }
38245
+ const sample = {
38246
+ ...stats,
38247
+ sampledAtMs: performance.now()
38248
+ };
38249
+ probe.latest = sample;
38250
+ probe.samples.push(sample);
38251
+ if (probe.samples.length > MAX_VIEWPORT_PERFORMANCE_SAMPLES) {
38252
+ probe.samples.splice(0, probe.samples.length - MAX_VIEWPORT_PERFORMANCE_SAMPLES);
38253
+ }
38254
+ }
38255
+ function isViewportPerformanceProbeEnabled() {
38256
+ if (typeof window === "undefined") return false;
38257
+ const params = new URLSearchParams(window.location.search);
38258
+ return params.has("viewport-fps") || params.has("viewportPerf") || window.localStorage.getItem("fc-viewport-performance-probe") === "1";
38259
+ }
37215
38260
  function PerformanceInfoSampler({
37216
38261
  enabled,
37217
38262
  modelTriangles,
@@ -37236,7 +38281,10 @@ function PerformanceInfoSampler({
37236
38281
  sinceEmitSec: 0,
37237
38282
  reactRenderCountAtLastEmit: reactRenderCountRef.current
37238
38283
  };
37239
- if (!enabled) onStatsChange(null);
38284
+ if (!enabled) {
38285
+ publishViewportPerformanceSample(null);
38286
+ onStatsChange(null);
38287
+ }
37240
38288
  }, [enabled, modelTriangles, onStatsChange, reactRenderCountRef, sceneObjects]);
37241
38289
  useFrame((_state, delta) => {
37242
38290
  var _a3, _b2, _c, _d;
@@ -37251,7 +38299,7 @@ function PerformanceInfoSampler({
37251
38299
  const reactRendersDelta = reactRenderCountRef.current - sample.reactRenderCountAtLastEmit;
37252
38300
  const mem = performance.memory;
37253
38301
  const cacheStats = getRunResultCacheStats();
37254
- onStatsChange({
38302
+ const stats = {
37255
38303
  fps: frameCount / Math.max(sample.elapsedSec, 1e-6),
37256
38304
  frameTimeMs: sample.frameTimeMsTotal / frameCount,
37257
38305
  sceneObjects,
@@ -37274,8 +38322,11 @@ function PerformanceInfoSampler({
37274
38322
  wasmHeapTruckMB: toMB(((_d = evalWorkerClient.workerWasmHeap) == null ? void 0 : _d.truckBytes) ?? null),
37275
38323
  workerFailure: evalWorkerClient.lastWorkerFailure,
37276
38324
  reactRendersPerSec: reactRendersDelta / Math.max(sample.sinceEmitSec, 1e-6),
37277
- scanProxy: collectScanProxyPerformanceInfo(scene)
37278
- });
38325
+ scanProxy: collectScanProxyPerformanceInfo(scene),
38326
+ sdfDistanceBricks: collectSdfDistanceBrickPerformanceInfo(scene)
38327
+ };
38328
+ publishViewportPerformanceSample(stats);
38329
+ onStatsChange(stats);
37279
38330
  sample.frames = 0;
37280
38331
  sample.elapsedSec = 0;
37281
38332
  sample.frameTimeMsTotal = 0;
@@ -37802,6 +38853,13 @@ function computeSceneObjectBounds(obj, objectMatrices) {
37802
38853
  }
37803
38854
  return isFiniteBox3(out) ? out : null;
37804
38855
  }
38856
+ if (obj.sdf) {
38857
+ const out = new Box3();
38858
+ if (!expandBoundsByTransformedAabb(out, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) {
38859
+ return null;
38860
+ }
38861
+ return isFiniteBox3(out) ? out : null;
38862
+ }
37805
38863
  if (obj.curve3d) {
37806
38864
  const b2 = obj.curve3d.bounds;
37807
38865
  const out = new Box3();
@@ -37814,12 +38872,6 @@ function computeSceneObjectBounds(obj, objectMatrices) {
37814
38872
  if (!expandBoundsByTransformedAabb(out, b2.min, b2.max, matrix)) return null;
37815
38873
  return isFiniteBox3(out) ? out : null;
37816
38874
  }
37817
- if (obj.sdf) {
37818
- const b2 = obj.sdf.bounds;
37819
- const out = new Box3();
37820
- if (!expandBoundsByTransformedAabb(out, b2.min, b2.max, matrix)) return null;
37821
- return isFiniteBox3(out) ? out : null;
37822
- }
37823
38875
  return null;
37824
38876
  }
37825
38877
  function computeSceneBounds(objects, objectMatrices) {
@@ -38259,290 +39311,6 @@ function SectionCaps({
38259
39311
  );
38260
39312
  }) });
38261
39313
  }
38262
- const DEFAULT_COLOR = "#5b9bd5";
38263
- const BLACK = [0, 0, 0];
38264
- const MAX_SDF_CLIP_PLANES = 8;
38265
- const PICK_MAX_STEPS = 220;
38266
- const PICK_MIN_STEP = 0.012;
38267
- const PICK_HIT_EPS = 0.05;
38268
- function colorToRgb(value, fallback = DEFAULT_COLOR) {
38269
- const color2 = new Color(value || fallback);
38270
- return [color2.r, color2.g, color2.b];
38271
- }
38272
- function clamp01(value) {
38273
- return Math.min(1, Math.max(0, value));
38274
- }
38275
- function computeBounds(leaves) {
38276
- if (leaves.length === 0) return null;
38277
- const min = [Infinity, Infinity, Infinity];
38278
- const max2 = [-Infinity, -Infinity, -Infinity];
38279
- for (const leaf of leaves) {
38280
- for (let i = 0; i < 3; i++) {
38281
- min[i] = Math.min(min[i], leaf.bounds.min[i]);
38282
- max2[i] = Math.max(max2[i], leaf.bounds.max[i]);
38283
- }
38284
- }
38285
- if (!min.every(Number.isFinite) || !max2.every(Number.isFinite)) return null;
38286
- const diagonal = Math.hypot(max2[0] - min[0], max2[1] - min[1], max2[2] - min[2]);
38287
- const pad = Math.max(1, diagonal * 0.04);
38288
- return {
38289
- min: [min[0] - pad, min[1] - pad, min[2] - pad],
38290
- max: [max2[0] + pad, max2[1] + pad, max2[2] + pad]
38291
- };
38292
- }
38293
- function setClipPlaneUniforms(target, clippingPlanes) {
38294
- const count = Math.min(clippingPlanes.length, MAX_SDF_CLIP_PLANES);
38295
- for (let i = 0; i < MAX_SDF_CLIP_PLANES; i++) {
38296
- const plane = clippingPlanes[i];
38297
- target[i].set((plane == null ? void 0 : plane.normal.x) ?? 0, (plane == null ? void 0 : plane.normal.y) ?? 0, (plane == null ? void 0 : plane.normal.z) ?? 0, (plane == null ? void 0 : plane.constant) ?? 0);
38298
- }
38299
- return count;
38300
- }
38301
- function clipPlaneDistance(point, clippingPlanes) {
38302
- let distance = -Infinity;
38303
- const count = Math.min(clippingPlanes.length, MAX_SDF_CLIP_PLANES);
38304
- for (let i = 0; i < count; i++) {
38305
- const plane = clippingPlanes[i];
38306
- distance = Math.max(distance, -plane.distanceToPoint(point));
38307
- }
38308
- return distance;
38309
- }
38310
- function clippedDistance(fn, point, clippingPlanes) {
38311
- return Math.max(fn(point.x, point.y, point.z), clipPlaneDistance(point, clippingPlanes));
38312
- }
38313
- function intersectRayBoxRange(ray, box2) {
38314
- let tNear = -Infinity;
38315
- let tFar = Infinity;
38316
- const origin = ray.origin;
38317
- const direction = ray.direction;
38318
- for (const axis of ["x", "y", "z"]) {
38319
- const o2 = origin[axis];
38320
- const d = direction[axis];
38321
- const min = box2.min[axis];
38322
- const max2 = box2.max[axis];
38323
- if (Math.abs(d) < 1e-10) {
38324
- if (o2 < min || o2 > max2) return null;
38325
- continue;
38326
- }
38327
- const inv = 1 / d;
38328
- let t02 = (min - o2) * inv;
38329
- let t1 = (max2 - o2) * inv;
38330
- if (t02 > t1) [t02, t1] = [t1, t02];
38331
- tNear = Math.max(tNear, t02);
38332
- tFar = Math.min(tFar, t1);
38333
- if (tFar < tNear) return null;
38334
- }
38335
- if (tFar < 0) return null;
38336
- return { near: Math.max(0, tNear), far: tFar };
38337
- }
38338
- function raymarchHit(ray, box2, diagonal, sdfFn, clippingPlanes) {
38339
- const range = intersectRayBoxRange(ray, box2);
38340
- if (!range) return null;
38341
- const maxStep = Math.max(PICK_MIN_STEP, diagonal / 180);
38342
- const point = new Vector3();
38343
- let t2 = range.near;
38344
- let prevT = t2;
38345
- let prevDist = Number.POSITIVE_INFINITY;
38346
- let bestT = t2;
38347
- let bestAbsDist = Number.POSITIVE_INFINITY;
38348
- for (let i = 0; i < PICK_MAX_STEPS; i++) {
38349
- point.copy(ray.origin).addScaledVector(ray.direction, t2);
38350
- const dist = clippedDistance(sdfFn, point, clippingPlanes);
38351
- const absDist = Math.abs(dist);
38352
- if (absDist < bestAbsDist) {
38353
- bestAbsDist = absDist;
38354
- bestT = t2;
38355
- }
38356
- if (absDist < PICK_HIT_EPS) return point;
38357
- if (Number.isFinite(prevDist) && (prevDist < 0 && dist > 0 || prevDist > 0 && dist < 0)) {
38358
- let lo = prevT;
38359
- let hi = t2;
38360
- const midPoint = point.clone();
38361
- let dLo = prevDist;
38362
- for (let step = 0; step < 8; step++) {
38363
- const mid = (lo + hi) * 0.5;
38364
- midPoint.copy(ray.origin).addScaledVector(ray.direction, mid);
38365
- const dMid = clippedDistance(sdfFn, midPoint, clippingPlanes);
38366
- if (Math.abs(dMid) < PICK_HIT_EPS) return midPoint;
38367
- if (dLo < 0 && dMid > 0 || dLo > 0 && dMid < 0) {
38368
- hi = mid;
38369
- } else {
38370
- lo = mid;
38371
- dLo = dMid;
38372
- }
38373
- }
38374
- return midPoint;
38375
- }
38376
- prevT = t2;
38377
- prevDist = dist;
38378
- t2 += MathUtils.clamp(absDist * 0.5, PICK_MIN_STEP, maxStep);
38379
- if (t2 > range.far) break;
38380
- }
38381
- return bestAbsDist < PICK_HIT_EPS * 2 ? ray.origin.clone().addScaledVector(ray.direction, bestT) : null;
38382
- }
38383
- function SdfRaymarchObject({
38384
- obj,
38385
- settings,
38386
- clippingPlanes,
38387
- isHovered,
38388
- onPointerEnter,
38389
- onPointerMove,
38390
- onPointerLeave,
38391
- onClick,
38392
- onDoubleClick,
38393
- onContextMenu
38394
- }) {
38395
- const meshRef = reactExports.useRef(null);
38396
- const sdf = obj.sdf;
38397
- const bounds = reactExports.useMemo(() => sdf ? computeBounds([sdf]) : null, [sdf]);
38398
- const boundsBox = reactExports.useMemo(
38399
- () => bounds ? new Box3(new Vector3(...bounds.min), new Vector3(...bounds.max)) : null,
38400
- [bounds]
38401
- );
38402
- const boundsDiagonal = reactExports.useMemo(() => boundsBox ? boundsBox.getSize(new Vector3()).length() : 0, [boundsBox]);
38403
- const sdfFn = reactExports.useMemo(() => sdf ? compileSdfNode3(sdf.node) : null, [sdf]);
38404
- const color2 = reactExports.useMemo(() => colorToRgb((settings == null ? void 0 : settings.color) ?? (sdf == null ? void 0 : sdf.colorHex)), [sdf == null ? void 0 : sdf.colorHex, settings == null ? void 0 : settings.color]);
38405
- const leaf = reactExports.useMemo(() => {
38406
- var _a3, _b2, _c, _d, _e, _f, _g, _h, _i;
38407
- if (!sdf) return null;
38408
- const materialAlpha = ((_a3 = sdf.materialProps) == null ? void 0 : _a3.opacity) ?? 1;
38409
- return {
38410
- node: sdf.node,
38411
- color: color2,
38412
- alpha: clamp01(Math.min((settings == null ? void 0 : settings.opacity) ?? 1, materialAlpha)),
38413
- emissive: ((_b2 = sdf.materialProps) == null ? void 0 : _b2.emissive) ? colorToRgb(sdf.materialProps.emissive, "#000000") : BLACK,
38414
- emissiveIntensity: ((_c = sdf.materialProps) == null ? void 0 : _c.emissiveIntensity) ?? 0,
38415
- metalness: clamp01(((_d = sdf.materialProps) == null ? void 0 : _d.metalness) ?? 0.05),
38416
- roughness: clamp01(((_e = sdf.materialProps) == null ? void 0 : _e.roughness) ?? 0.35),
38417
- clearcoat: clamp01(((_f = sdf.materialProps) == null ? void 0 : _f.clearcoat) ?? 0.1),
38418
- clearcoatRoughness: clamp01(((_g = sdf.materialProps) == null ? void 0 : _g.clearcoatRoughness) ?? 0.4),
38419
- transmission: clamp01(((_h = sdf.materialProps) == null ? void 0 : _h.transmission) ?? 0),
38420
- reflectivity: clamp01(((_i = sdf.materialProps) == null ? void 0 : _i.reflectivity) ?? 0.5)
38421
- };
38422
- }, [color2, sdf, settings == null ? void 0 : settings.opacity]);
38423
- const isTransparent = ((leaf == null ? void 0 : leaf.alpha) ?? 1) < 1 || ((leaf == null ? void 0 : leaf.transmission) ?? 0) > 0;
38424
- const fragmentShader2 = reactExports.useMemo(() => leaf ? buildSdfRaymarchFragmentShader([leaf]) : null, [leaf]);
38425
- const clipPlaneUniforms = reactExports.useMemo(() => Array.from({ length: MAX_SDF_CLIP_PLANES }, () => new Vector4()), []);
38426
- const material = reactExports.useMemo(() => {
38427
- if (!fragmentShader2) return null;
38428
- return new ShaderMaterial({
38429
- vertexShader: SDF_RAYMARCH_PROXY_VERTEX_SHADER,
38430
- fragmentShader: fragmentShader2,
38431
- side: BackSide,
38432
- transparent: isTransparent,
38433
- depthTest: true,
38434
- // SDF previews render a proxy box, but the shader writes the actual
38435
- // raymarched hit depth. Keep depth writes on for transparent SDFs so
38436
- // overlapping leaves sort by their real surface depth instead of by the
38437
- // proxy box centers used by Three's transparent object sorter.
38438
- depthWrite: true,
38439
- uniforms: {
38440
- uCameraMatrixWorld: { value: new Matrix4() },
38441
- uViewProjectionMatrix: { value: new Matrix4() },
38442
- uSceneMin: { value: new Vector3() },
38443
- uSceneMax: { value: new Vector3() },
38444
- uClipPlaneCount: { value: 0 },
38445
- uClipPlanes: { value: clipPlaneUniforms },
38446
- uHoverLeafIndex: { value: -1 },
38447
- uHoverColor: { value: new Vector3() },
38448
- uHoverIntensity: { value: 0 },
38449
- uIsOrthographic: { value: 0 }
38450
- }
38451
- });
38452
- }, [clipPlaneUniforms, fragmentShader2, isTransparent]);
38453
- reactExports.useEffect(() => () => material == null ? void 0 : material.dispose(), [material]);
38454
- useFrame(({ camera }) => {
38455
- if (!bounds || !material) return;
38456
- material.uniforms.uCameraMatrixWorld.value.copy(camera.matrixWorld);
38457
- material.uniforms.uViewProjectionMatrix.value.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
38458
- material.uniforms.uSceneMin.value.set(bounds.min[0], bounds.min[1], bounds.min[2]);
38459
- material.uniforms.uSceneMax.value.set(bounds.max[0], bounds.max[1], bounds.max[2]);
38460
- material.uniforms.uClipPlaneCount.value = setClipPlaneUniforms(clipPlaneUniforms, clippingPlanes);
38461
- material.uniforms.uHoverLeafIndex.value = isHovered ? 0 : -1;
38462
- material.uniforms.uHoverColor.value.set(color2[0], color2[1], color2[2]);
38463
- material.uniforms.uHoverIntensity.value = isHovered ? 0.3 : 0;
38464
- material.uniforms.uIsOrthographic.value = camera.isOrthographicCamera ? 1 : 0;
38465
- });
38466
- const raycast = reactExports.useCallback(
38467
- (raycaster, intersections) => {
38468
- const object = meshRef.current;
38469
- if (!object || !boundsBox || !sdfFn) return;
38470
- const point = raymarchHit(raycaster.ray, boundsBox, boundsDiagonal, sdfFn, clippingPlanes);
38471
- if (!point) return;
38472
- const distance = raycaster.ray.origin.distanceTo(point);
38473
- if (distance < raycaster.near || distance > raycaster.far) return;
38474
- intersections.push({ distance, point, object });
38475
- },
38476
- [boundsBox, boundsDiagonal, clippingPlanes, sdfFn]
38477
- );
38478
- if (!bounds || !material || !sdfFn) return null;
38479
- const size = [
38480
- Math.max(bounds.max[0] - bounds.min[0], 1e-3),
38481
- Math.max(bounds.max[1] - bounds.min[1], 1e-3),
38482
- Math.max(bounds.max[2] - bounds.min[2], 1e-3)
38483
- ];
38484
- const center = [
38485
- (bounds.min[0] + bounds.max[0]) * 0.5,
38486
- (bounds.min[1] + bounds.max[1]) * 0.5,
38487
- (bounds.min[2] + bounds.max[2]) * 0.5
38488
- ];
38489
- return /* @__PURE__ */ jsxRuntimeExports.jsxs(
38490
- "mesh",
38491
- {
38492
- ref: meshRef,
38493
- position: center,
38494
- frustumCulled: false,
38495
- raycast,
38496
- onPointerEnter,
38497
- onPointerMove,
38498
- onPointerLeave,
38499
- onClick,
38500
- onDoubleClick,
38501
- onContextMenu,
38502
- children: [
38503
- /* @__PURE__ */ jsxRuntimeExports.jsx("boxGeometry", { args: size }),
38504
- /* @__PURE__ */ jsxRuntimeExports.jsx("primitive", { object: material, attach: "material" })
38505
- ]
38506
- }
38507
- );
38508
- }
38509
- function SdfRaymarchLayer({
38510
- objects,
38511
- objectSettings,
38512
- clippingPlanesById,
38513
- hoveredObjectId,
38514
- onPointerEnter,
38515
- onPointerMove,
38516
- onPointerLeave,
38517
- onClick,
38518
- onDoubleClick,
38519
- onContextMenu
38520
- }) {
38521
- const visibleSdfObjects = reactExports.useMemo(
38522
- () => objects.filter((obj) => {
38523
- var _a3;
38524
- return obj.sdf && (((_a3 = objectSettings[obj.id]) == null ? void 0 : _a3.visible) ?? true);
38525
- }),
38526
- [objectSettings, objects]
38527
- );
38528
- if (visibleSdfObjects.length === 0) return null;
38529
- return /* @__PURE__ */ jsxRuntimeExports.jsx("group", { children: visibleSdfObjects.map((obj) => /* @__PURE__ */ jsxRuntimeExports.jsx(
38530
- SdfRaymarchObject,
38531
- {
38532
- obj,
38533
- settings: objectSettings[obj.id],
38534
- clippingPlanes: clippingPlanesById[obj.id] ?? EMPTY_CLIPPING_PLANES,
38535
- isHovered: hoveredObjectId === obj.id,
38536
- onPointerEnter: onPointerEnter ? (event) => onPointerEnter(obj, event) : void 0,
38537
- onPointerMove: onPointerMove ? (event) => onPointerMove(obj, event) : void 0,
38538
- onPointerLeave: onPointerLeave ? (event) => onPointerLeave(obj, event) : void 0,
38539
- onClick: onClick ? (event) => onClick(obj, event) : void 0,
38540
- onDoubleClick: onDoubleClick ? (event) => onDoubleClick(obj, event) : void 0,
38541
- onContextMenu: onContextMenu ? (event) => onContextMenu(obj, event) : void 0
38542
- },
38543
- obj.id
38544
- )) });
38545
- }
38546
39314
  function findHoveredSurface(x, y, meta) {
38547
39315
  for (let i = meta.surfaces.length - 1; i >= 0; i--) {
38548
39316
  const s = meta.surfaces[i];
@@ -42395,6 +43163,10 @@ function useViewportState() {
42395
43163
  }
42396
43164
  return;
42397
43165
  }
43166
+ if (obj.sdf) {
43167
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) hasBounds = true;
43168
+ return;
43169
+ }
42398
43170
  if (obj.sketch) {
42399
43171
  try {
42400
43172
  const bb = obj.sketch.bounds();
@@ -42403,15 +43175,16 @@ function useViewportState() {
42403
43175
  }
42404
43176
  return;
42405
43177
  }
43178
+ if (obj.curve3d) {
43179
+ const cb = obj.curve3d.bounds;
43180
+ if (expandBoundsByTransformedAabb(bounds, cb.min, cb.max, matrix)) hasBounds = true;
43181
+ return;
43182
+ }
42406
43183
  if (obj.toolpath) {
42407
43184
  const tb = obj.toolpath.bounds;
42408
43185
  if (expandBoundsByTransformedAabb(bounds, tb.min, tb.max, matrix)) hasBounds = true;
42409
43186
  return;
42410
43187
  }
42411
- if (obj.sdf) {
42412
- const sb = obj.sdf.bounds;
42413
- if (expandBoundsByTransformedAabb(bounds, sb.min, sb.max, matrix)) hasBounds = true;
42414
- }
42415
43188
  });
42416
43189
  return hasBounds ? bounds.min.z : null;
42417
43190
  }, [objects, objectMatrices]);
@@ -42428,6 +43201,10 @@ function useViewportState() {
42428
43201
  if (expandBoundsByTransformedAabb(bounds, bb.min, bb.max, matrix)) hasBounds = true;
42429
43202
  } catch {
42430
43203
  }
43204
+ return;
43205
+ }
43206
+ if (obj.sdf) {
43207
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) hasBounds = true;
42431
43208
  }
42432
43209
  });
42433
43210
  if (!hasBounds) return { surfaceFieldScale: 1, scanProxyGrid: null };
@@ -42455,6 +43232,10 @@ function useViewportState() {
42455
43232
  }
42456
43233
  return;
42457
43234
  }
43235
+ if (obj.sdf) {
43236
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, matrix)) hasBounds = true;
43237
+ return;
43238
+ }
42458
43239
  if (obj.sketch) {
42459
43240
  try {
42460
43241
  const bb = obj.sketch.bounds();
@@ -42463,15 +43244,16 @@ function useViewportState() {
42463
43244
  }
42464
43245
  return;
42465
43246
  }
43247
+ if (obj.curve3d) {
43248
+ const cb = obj.curve3d.bounds;
43249
+ if (expandBoundsByTransformedAabb(bounds, cb.min, cb.max, matrix)) hasBounds = true;
43250
+ return;
43251
+ }
42466
43252
  if (obj.toolpath) {
42467
43253
  const tb = obj.toolpath.bounds;
42468
43254
  if (expandBoundsByTransformedAabb(bounds, tb.min, tb.max, matrix)) hasBounds = true;
42469
43255
  return;
42470
43256
  }
42471
- if (obj.sdf) {
42472
- const sb = obj.sdf.bounds;
42473
- if (expandBoundsByTransformedAabb(bounds, sb.min, sb.max, matrix)) hasBounds = true;
42474
- }
42475
43257
  });
42476
43258
  if (!hasBounds) return Math.max(60, gridSize * 8);
42477
43259
  const size = new Vector3();
@@ -42491,6 +43273,10 @@ function useViewportState() {
42491
43273
  }
42492
43274
  return;
42493
43275
  }
43276
+ if (obj.sdf) {
43277
+ if (expandBoundsByTransformedAabb(bounds, obj.sdf.bounds.min, obj.sdf.bounds.max, IDENTITY_MATRIX$2)) hasBounds = true;
43278
+ return;
43279
+ }
42494
43280
  if (obj.sketch) {
42495
43281
  try {
42496
43282
  const bb = obj.sketch.bounds();
@@ -42506,15 +43292,16 @@ function useViewportState() {
42506
43292
  }
42507
43293
  return;
42508
43294
  }
43295
+ if (obj.curve3d) {
43296
+ const cb = obj.curve3d.bounds;
43297
+ if (expandBoundsByTransformedAabb(bounds, cb.min, cb.max, IDENTITY_MATRIX$2)) hasBounds = true;
43298
+ return;
43299
+ }
42509
43300
  if (obj.toolpath) {
42510
43301
  const tb = obj.toolpath.bounds;
42511
43302
  if (expandBoundsByTransformedAabb(bounds, tb.min, tb.max, IDENTITY_MATRIX$2)) hasBounds = true;
42512
43303
  return;
42513
43304
  }
42514
- if (obj.sdf) {
42515
- const sb = obj.sdf.bounds;
42516
- if (expandBoundsByTransformedAabb(bounds, sb.min, sb.max, IDENTITY_MATRIX$2)) hasBounds = true;
42517
- }
42518
43305
  });
42519
43306
  if (!hasBounds) return Math.max(60, gridSize * 8);
42520
43307
  const size = new Vector3();
@@ -42587,8 +43374,7 @@ function useViewportState() {
42587
43374
  ]);
42588
43375
  const rigInspectionBounds = rigInspectionOverlay.bounds;
42589
43376
  const hasShape = objects.some((obj) => obj.shape);
42590
- const hasSdf = objects.some((obj) => obj.sdf);
42591
- const isSketchOnly = !hasShape && !hasSdf && objects.some((obj) => obj.sketch);
43377
+ const isSketchOnly = !hasShape && objects.some((obj) => obj.sketch);
42592
43378
  const knownFileNames = reactExports.useMemo(() => new Set(Object.keys(files)), [files]);
42593
43379
  const focusedObjectIdSet = reactExports.useMemo(() => new Set(focusedObjectIds), [focusedObjectIds]);
42594
43380
  const { visibleSceneObjectCount, visibleModelTriangles } = reactExports.useMemo(() => {
@@ -44273,7 +45059,7 @@ function useGeometryComparison(args) {
44273
45059
  class InspectWorkerClient {
44274
45060
  constructor(workerFactory = () => new Worker(new URL(
44275
45061
  /* @vite-ignore */
44276
- "/assets/inspectWorker-CZsCFtQT.js",
45062
+ "/assets/inspectWorker-D5T5VbfK.js",
44277
45063
  import.meta.url
44278
45064
  ), { type: "module" })) {
44279
45065
  __publicField(this, "worker", null);
@@ -45014,6 +45800,7 @@ function VoxelIntentOverlay() {
45014
45800
  ))
45015
45801
  ] });
45016
45802
  }
45803
+ const DEFAULT_OBJECT_SETTINGS = { visible: true, opacity: 1, color: "#5b9bd5" };
45017
45804
  function buildManualSceneConfig(base, manualScene, renderStylePreset) {
45018
45805
  var _a3, _b2, _c;
45019
45806
  const lights = [
@@ -45424,6 +46211,7 @@ function Viewport() {
45424
46211
  previewFile,
45425
46212
  knownFileNames
45426
46213
  } = state2;
46214
+ const viewportPerformanceProbeEnabled = reactExports.useMemo(() => isViewportPerformanceProbeEnabled(), []);
45427
46215
  const voxelContextFile = previewFile ?? ((activeFile == null ? void 0 : activeFile.toLowerCase().endsWith(".forge.js")) ? activeFile : null);
45428
46216
  const historyMode = useForgeStore((s) => s.historyMode);
45429
46217
  const historyObjectIds = useForgeStore((s) => s.historyObjectIds);
@@ -45464,10 +46252,6 @@ function Viewport() {
45464
46252
  const inspectChannelActive = inspectChannel !== "none";
45465
46253
  const comparisonInspectActive = inspectChannel === "comparison";
45466
46254
  const rigInspectActive = inspectChannel === "rig";
45467
- const hasVisibleSdfObjects = objects.some((obj) => {
45468
- var _a4;
45469
- return obj.sdf && (((_a4 = objectSettings[obj.id]) == null ? void 0 : _a4.visible) ?? true);
45470
- });
45471
46255
  const viewportDisabledMessage = reactExports.useMemo(() => {
45472
46256
  if (activeFile && !isModelFile(activeFile) && !isSvgActive && !meshPreviewFile) {
45473
46257
  return {
@@ -45483,15 +46267,12 @@ function Viewport() {
45483
46267
  }
45484
46268
  return null;
45485
46269
  }, [activeFile, isSvgActive, meshPreviewFile, objects.length, result, rigInspectActive]);
45486
- const canvasDpr = reactExports.useMemo(() => {
45487
- if (hasVisibleSdfObjects) return renderStylePreset.canvasDpr.live;
45488
- return [1, renderStylePreset.canvasDpr.idleMax];
45489
- }, [hasVisibleSdfObjects, renderStylePreset.canvasDpr.idleMax, renderStylePreset.canvasDpr.live]);
45490
- const effectiveSdfObjectSettings = reactExports.useMemo(() => {
46270
+ const canvasDpr = isViewportInteracting ? renderStylePreset.canvasDpr.live : [1, renderStylePreset.canvasDpr.idleMax];
46271
+ const sdfObjectSettings = reactExports.useMemo(() => {
45491
46272
  const next = {};
45492
46273
  for (const obj of objects) {
45493
46274
  if (!obj.sdf) continue;
45494
- const settings = objectSettings[obj.id] ?? { visible: true, opacity: 1, color: "#5b9bd5" };
46275
+ const settings = objectSettings[obj.id] ?? DEFAULT_OBJECT_SETTINGS;
45495
46276
  const isDimmedByFocus = focusedObjectIdSet.size > 0 && !focusedObjectIdSet.has(obj.id);
45496
46277
  const isDimmedByGhost = constructionGhost !== null && obj.id !== constructionGhost.objectId;
45497
46278
  next[obj.id] = isDimmedByFocus || isDimmedByGhost ? { ...settings, opacity: Math.min(settings.opacity, FOCUS_MODE_DIM_OPACITY) } : settings;
@@ -45886,7 +46667,7 @@ function Viewport() {
45886
46667
  }
45887
46668
  ),
45888
46669
  objects.map((obj, objIndex) => {
45889
- const settings = objectSettings[obj.id] ?? { visible: true, opacity: 1, color: "#5b9bd5" };
46670
+ const settings = objectSettings[obj.id] ?? DEFAULT_OBJECT_SETTINGS;
45890
46671
  const isDimmedByFocus = focusedObjectIdSet.size > 0 && !focusedObjectIdSet.has(obj.id);
45891
46672
  const isDimmedByGhost = constructionGhost !== null && obj.id !== constructionGhost.objectId;
45892
46673
  const isDimmedByVoxel = voxelIntentMode && !!obj.shape;
@@ -45936,6 +46717,26 @@ function Viewport() {
45936
46717
  obj.id
45937
46718
  );
45938
46719
  }
46720
+ if (canRenderSdfDirectly(obj.sdf)) {
46721
+ return /* @__PURE__ */ jsxRuntimeExports.jsx(
46722
+ SdfDirectObject,
46723
+ {
46724
+ sdf: obj.sdf,
46725
+ settings: sdfObjectSettings[obj.id] ?? DEFAULT_OBJECT_SETTINGS,
46726
+ matrix,
46727
+ materialProps: obj.materialProps,
46728
+ isHovered,
46729
+ clippingPlanes: objectClippingPlanes,
46730
+ onPointerEnter: (event) => updateHoverLabel(obj, event),
46731
+ onPointerMove: (event) => updateHoverLabel(obj, event),
46732
+ onPointerLeave: (event) => clearHoverLabel(obj, event),
46733
+ onClick: (event) => handleObjectClick(obj, event),
46734
+ onDoubleClick: (event) => handleObjectDoubleClick(obj, event),
46735
+ onContextMenu: (event) => handleObjectContextMenu(obj, event)
46736
+ },
46737
+ obj.id
46738
+ );
46739
+ }
45939
46740
  if (obj.sketch) {
45940
46741
  return /* @__PURE__ */ jsxRuntimeExports.jsx(
45941
46742
  SketchObject,
@@ -46049,7 +46850,7 @@ function Viewport() {
46049
46850
  /* @__PURE__ */ jsxRuntimeExports.jsx(
46050
46851
  PerformanceInfoSampler,
46051
46852
  {
46052
- enabled: showPerformanceInfo,
46853
+ enabled: showPerformanceInfo || viewportPerformanceProbeEnabled,
46053
46854
  sceneObjects: visibleSceneObjectCount,
46054
46855
  modelTriangles: visibleModelTriangles,
46055
46856
  reactRenderCountRef,
@@ -46072,21 +46873,6 @@ function Viewport() {
46072
46873
  infiniteGrid: true
46073
46874
  }
46074
46875
  ),
46075
- !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(
46076
- SdfRaymarchLayer,
46077
- {
46078
- objects,
46079
- objectSettings: effectiveSdfObjectSettings,
46080
- clippingPlanesById: objectClippingPlanesById,
46081
- hoveredObjectId,
46082
- onPointerEnter: (obj, event) => updateHoverLabel(obj, event),
46083
- onPointerMove: (obj, event) => updateHoverLabel(obj, event),
46084
- onPointerLeave: (obj, event) => clearHoverLabel(obj, event),
46085
- onClick: (obj, event) => voxelIntentMode ? handleVoxelObjectSurfaceClick(event, objectMatrices[obj.id] ?? new Matrix4()) : handleObjectClick(obj, event),
46086
- onDoubleClick: (obj, event) => handleObjectDoubleClick(obj, event),
46087
- onContextMenu: (obj, event) => handleObjectContextMenu(obj, event)
46088
- }
46089
- ),
46090
46876
  axesVisible && !isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(LabeledAxes, {}),
46091
46877
  axesVisible && isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(SketchAxes, {}),
46092
46878
  isSketchOnly && /* @__PURE__ */ jsxRuntimeExports.jsx(CursorTracker, { onMove: (x, y) => setCursorPos({ x, y }) }),
@@ -46702,7 +47488,7 @@ function Viewport() {
46702
47488
  }
46703
47489
  );
46704
47490
  }
46705
- const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BJ0Dloyh.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
47491
+ const EditorApp$1 = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-Bfd3jbtC.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46706
47492
  const PENDING_SHARE_COPY_KEY = "fc-pending-share-copy";
46707
47493
  function storePendingShareCopy(shareId) {
46708
47494
  sessionStorage.setItem(PENDING_SHARE_COPY_KEY, shareId);
@@ -46736,7 +47522,7 @@ function PublishedModelPage() {
46736
47522
  checkSession();
46737
47523
  }, [checkSession]);
46738
47524
  reactExports.useEffect(() => {
46739
- Promise.all([initKernel(), initSolverWasm()]).then(() => {
47525
+ initBackendForEvaluation(useForgeStore.getState().activeBackend).then(() => {
46740
47526
  setKernelReady(true);
46741
47527
  });
46742
47528
  }, []);
@@ -46968,17 +47754,17 @@ function SeoMetadata() {
46968
47754
  }, [location.pathname]);
46969
47755
  return null;
46970
47756
  }
46971
- reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-BxHkYRE7.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
46972
- const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-CDoxHkz8.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
46973
- reactExports.lazy(() => __vitePreload(() => import("./BlogPage-DHaGP50_.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
46974
- reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-BVEpJSVk.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
46975
- reactExports.lazy(() => __vitePreload(() => import("./AdminPage-DcCnj0qo.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
47757
+ reactExports.lazy(() => __vitePreload(() => import("./LandingPageProofDriven-DbN7o-Be.js"), true ? __vite__mapDeps([1]) : void 0).then((m2) => ({ default: m2.LandingPageProofDriven })));
47758
+ const DocsPage = reactExports.lazy(() => __vitePreload(() => import("./DocsPage-DsvdiRNK.js"), true ? [] : void 0).then((m2) => ({ default: m2.DocsPage })));
47759
+ reactExports.lazy(() => __vitePreload(() => import("./BlogPage-BssBbnb-.js"), true ? [] : void 0).then((m2) => ({ default: m2.BlogPage })));
47760
+ reactExports.lazy(() => __vitePreload(() => import("./BenchmarkPage-BcRT5iGN.js"), true ? __vite__mapDeps([1,2]) : void 0).then((m2) => ({ default: m2.BenchmarkPage })));
47761
+ reactExports.lazy(() => __vitePreload(() => import("./AdminPage-CHY6ZN-p.js"), true ? [] : void 0).then((m2) => ({ default: m2.AdminPage })));
46976
47762
  reactExports.lazy(() => __vitePreload(() => Promise.resolve().then(() => PublishedModelPage$1), true ? void 0 : void 0).then((m2) => ({ default: m2.PublishedModelPage })));
46977
- reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-CIZSSAd0.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
46978
- reactExports.lazy(() => __vitePreload(() => import("./PricingPage-CzpZ6-Ce.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
46979
- reactExports.lazy(() => __vitePreload(() => import("./LegalPage-B-u6FrVv.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
46980
- const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-BJ0Dloyh.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
46981
- const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-CRKZbY0y.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
47763
+ reactExports.lazy(() => __vitePreload(() => import("./SettingsPage-DZlyu4d4.js"), true ? [] : void 0).then((m2) => ({ default: m2.SettingsPage })));
47764
+ reactExports.lazy(() => __vitePreload(() => import("./PricingPage-Nczr3pRz.js"), true ? __vite__mapDeps([1,3]) : void 0).then((m2) => ({ default: m2.PricingPage })));
47765
+ reactExports.lazy(() => __vitePreload(() => import("./LegalPage-DNGrrY0p.js"), true ? __vite__mapDeps([1,4]) : void 0).then((m2) => ({ default: m2.LegalPage })));
47766
+ const EditorApp = reactExports.lazy(() => __vitePreload(() => import("./EditorApp-Bfd3jbtC.js"), true ? __vite__mapDeps([0]) : void 0).then((m2) => ({ default: m2.EditorApp })));
47767
+ const EmbedViewer = reactExports.lazy(() => __vitePreload(() => import("./EmbedViewer-D5t8WamV.js"), true ? [] : void 0).then((m2) => ({ default: m2.EmbedViewer })));
46982
47768
  const embedMode = isEmbedMode() && !window.location.pathname.startsWith("/m/");
46983
47769
  const EDITABLE_CRASH_FILE = /\.(?:forge\.js|[cm]?[jt]sx?|json|md|txt|svg|dxf)$/i;
46984
47770
  function firstMeaningfulLine(text) {