brep-io-kernel 1.0.0-ci.10

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 (271) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +157 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/package.json +58 -0
  5. package/src/BREP/AssemblyComponent.js +42 -0
  6. package/src/BREP/BREP.js +43 -0
  7. package/src/BREP/BetterSolid.js +805 -0
  8. package/src/BREP/Edge.js +103 -0
  9. package/src/BREP/Extrude.js +403 -0
  10. package/src/BREP/Face.js +187 -0
  11. package/src/BREP/MeshRepairer.js +634 -0
  12. package/src/BREP/OffsetShellSolid.js +614 -0
  13. package/src/BREP/PointCloudWrap.js +302 -0
  14. package/src/BREP/Revolve.js +345 -0
  15. package/src/BREP/SolidMethods/authoring.js +112 -0
  16. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  17. package/src/BREP/SolidMethods/chamfer.js +122 -0
  18. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  19. package/src/BREP/SolidMethods/fillet.js +792 -0
  20. package/src/BREP/SolidMethods/index.js +72 -0
  21. package/src/BREP/SolidMethods/io.js +105 -0
  22. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  23. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  24. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  25. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  26. package/src/BREP/SolidMethods/metadata.js +106 -0
  27. package/src/BREP/SolidMethods/metrics.js +51 -0
  28. package/src/BREP/SolidMethods/transforms.js +361 -0
  29. package/src/BREP/SolidMethods/visualize.js +508 -0
  30. package/src/BREP/SolidShared.js +26 -0
  31. package/src/BREP/Sweep.js +1596 -0
  32. package/src/BREP/Tube.js +857 -0
  33. package/src/BREP/Vertex.js +43 -0
  34. package/src/BREP/applyBooleanOperation.js +704 -0
  35. package/src/BREP/boundsUtils.js +48 -0
  36. package/src/BREP/chamfer.js +551 -0
  37. package/src/BREP/edgePolylineUtils.js +85 -0
  38. package/src/BREP/fillets/common.js +388 -0
  39. package/src/BREP/fillets/fillet.js +1422 -0
  40. package/src/BREP/fillets/filletGeometry.js +15 -0
  41. package/src/BREP/fillets/inset.js +389 -0
  42. package/src/BREP/fillets/offsetHelper.js +143 -0
  43. package/src/BREP/fillets/outset.js +88 -0
  44. package/src/BREP/helix.js +193 -0
  45. package/src/BREP/meshToBrep.js +234 -0
  46. package/src/BREP/primitives.js +279 -0
  47. package/src/BREP/setupManifold.js +71 -0
  48. package/src/BREP/threadGeometry.js +1120 -0
  49. package/src/BREP/triangleUtils.js +8 -0
  50. package/src/BREP/triangulate.js +608 -0
  51. package/src/FeatureRegistry.js +183 -0
  52. package/src/PartHistory.js +1132 -0
  53. package/src/UI/AccordionWidget.js +292 -0
  54. package/src/UI/CADmaterials.js +850 -0
  55. package/src/UI/EnvMonacoEditor.js +522 -0
  56. package/src/UI/FloatingWindow.js +396 -0
  57. package/src/UI/HistoryWidget.js +457 -0
  58. package/src/UI/MainToolbar.js +131 -0
  59. package/src/UI/ModelLibraryView.js +194 -0
  60. package/src/UI/OrthoCameraIdle.js +206 -0
  61. package/src/UI/PluginsWidget.js +280 -0
  62. package/src/UI/SceneListing.js +606 -0
  63. package/src/UI/SelectionFilter.js +629 -0
  64. package/src/UI/ViewCube.js +389 -0
  65. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  66. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  67. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  68. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  69. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  70. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  71. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  72. package/src/UI/assembly/constraintPointUtils.js +64 -0
  73. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  74. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  75. package/src/UI/componentSelectorModal.js +240 -0
  76. package/src/UI/controls/CombinedTransformControls.js +386 -0
  77. package/src/UI/dialogs.js +351 -0
  78. package/src/UI/expressionsManager.js +100 -0
  79. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  80. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  81. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  82. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  83. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  84. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  85. package/src/UI/featureDialogWidgets/index.js +34 -0
  86. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  87. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  88. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  89. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  90. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  91. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  92. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  93. package/src/UI/featureDialogWidgets/utils.js +43 -0
  94. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  95. package/src/UI/featureDialogs.js +1414 -0
  96. package/src/UI/fileManagerWidget.js +615 -0
  97. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  98. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  99. package/src/UI/history/historyDisplayInfo.js +133 -0
  100. package/src/UI/mobile.js +28 -0
  101. package/src/UI/objectDump.js +442 -0
  102. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  103. package/src/UI/pmi/AnnotationHistory.js +353 -0
  104. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  105. package/src/UI/pmi/BaseAnnotation.js +269 -0
  106. package/src/UI/pmi/LabelOverlay.css +102 -0
  107. package/src/UI/pmi/LabelOverlay.js +191 -0
  108. package/src/UI/pmi/PMIMode.js +1550 -0
  109. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  110. package/src/UI/pmi/annUtils.js +729 -0
  111. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  112. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  113. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  114. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  115. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  116. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  117. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  118. package/src/UI/pmi/pmiStyle.js +44 -0
  119. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  120. package/src/UI/sketcher/dimensions.js +674 -0
  121. package/src/UI/sketcher/glyphs.js +236 -0
  122. package/src/UI/sketcher/highlights.js +60 -0
  123. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  124. package/src/UI/toolbarButtons/exportButton.js +609 -0
  125. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  126. package/src/UI/toolbarButtons/importButton.js +160 -0
  127. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  128. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  129. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  130. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  131. package/src/UI/toolbarButtons/saveButton.js +99 -0
  132. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  133. package/src/UI/toolbarButtons/testsButton.js +26 -0
  134. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  135. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  136. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  137. package/src/UI/triangleDebuggerWindow.js +945 -0
  138. package/src/UI/viewer.js +4228 -0
  139. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  140. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  141. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  142. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  143. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  144. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  145. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  146. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  147. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  148. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  149. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  150. package/src/core/entities/HistoryCollectionBase.js +72 -0
  151. package/src/core/entities/ListEntityBase.js +109 -0
  152. package/src/core/entities/schemaProcesser.js +121 -0
  153. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  154. package/src/exporters/sheetMetalUnfold.js +862 -0
  155. package/src/exporters/step.js +1135 -0
  156. package/src/exporters/threeMF.js +575 -0
  157. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  158. package/src/features/boolean/BooleanFeature.js +94 -0
  159. package/src/features/chamfer/ChamferFeature.js +116 -0
  160. package/src/features/datium/DatiumFeature.js +80 -0
  161. package/src/features/edgeFeatureUtils.js +41 -0
  162. package/src/features/extrude/ExtrudeFeature.js +143 -0
  163. package/src/features/fillet/FilletFeature.js +197 -0
  164. package/src/features/helix/HelixFeature.js +405 -0
  165. package/src/features/hole/HoleFeature.js +1050 -0
  166. package/src/features/hole/screwClearance.js +86 -0
  167. package/src/features/hole/threadDesignationCatalog.js +149 -0
  168. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  169. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  170. package/src/features/imageToFace/imageEditor.js +1270 -0
  171. package/src/features/imageToFace/traceUtils.js +971 -0
  172. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  173. package/src/features/loft/LoftFeature.js +605 -0
  174. package/src/features/mirror/MirrorFeature.js +151 -0
  175. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  176. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  177. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  178. package/src/features/pattern/PatternFeature.js +275 -0
  179. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  180. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  181. package/src/features/plane/PlaneFeature.js +154 -0
  182. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  183. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  184. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  185. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  186. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  187. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  188. package/src/features/remesh/RemeshFeature.js +97 -0
  189. package/src/features/revolve/RevolveFeature.js +111 -0
  190. package/src/features/selectionUtils.js +118 -0
  191. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  192. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  193. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  194. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  195. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  196. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  197. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  198. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  199. package/src/features/sheetMetal/profileUtils.js +25 -0
  200. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  201. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  202. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  203. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  204. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  205. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  206. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  207. package/src/features/sketch/SketchFeature.js +955 -0
  208. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  209. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  210. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  211. package/src/features/spline/SplineEditorSession.js +988 -0
  212. package/src/features/spline/SplineFeature.js +1388 -0
  213. package/src/features/spline/splineUtils.js +218 -0
  214. package/src/features/sweep/SweepFeature.js +110 -0
  215. package/src/features/transform/TransformFeature.js +152 -0
  216. package/src/features/tube/TubeFeature.js +635 -0
  217. package/src/fs.proxy.js +625 -0
  218. package/src/idbStorage.js +254 -0
  219. package/src/index.js +12 -0
  220. package/src/main.js +15 -0
  221. package/src/metadataManager.js +64 -0
  222. package/src/path.proxy.js +277 -0
  223. package/src/plugins/ghLoader.worker.js +151 -0
  224. package/src/plugins/pluginManager.js +286 -0
  225. package/src/pmi/PMIViewsManager.js +134 -0
  226. package/src/services/componentLibrary.js +198 -0
  227. package/src/tests/ConsoleCapture.js +189 -0
  228. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  229. package/src/tests/browserTests.js +597 -0
  230. package/src/tests/debugBoolean.js +225 -0
  231. package/src/tests/partFiles/badBoolean.json +957 -0
  232. package/src/tests/partFiles/extrudeTest.json +88 -0
  233. package/src/tests/partFiles/filletFail.json +58 -0
  234. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  235. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  236. package/src/tests/test_boolean_subtract.js +27 -0
  237. package/src/tests/test_chamfer.js +17 -0
  238. package/src/tests/test_extrudeFace.js +24 -0
  239. package/src/tests/test_fillet.js +17 -0
  240. package/src/tests/test_fillet_nonClosed.js +45 -0
  241. package/src/tests/test_filletsMoreDifficult.js +46 -0
  242. package/src/tests/test_history_features_basic.js +149 -0
  243. package/src/tests/test_hole.js +282 -0
  244. package/src/tests/test_mirror.js +16 -0
  245. package/src/tests/test_offsetShellGrouping.js +85 -0
  246. package/src/tests/test_plane.js +4 -0
  247. package/src/tests/test_primitiveCone.js +11 -0
  248. package/src/tests/test_primitiveCube.js +7 -0
  249. package/src/tests/test_primitiveCylinder.js +8 -0
  250. package/src/tests/test_primitivePyramid.js +9 -0
  251. package/src/tests/test_primitiveSphere.js +17 -0
  252. package/src/tests/test_primitiveTorus.js +21 -0
  253. package/src/tests/test_pushFace.js +126 -0
  254. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  255. package/src/tests/test_sheetMetal_features.js +80 -0
  256. package/src/tests/test_sketch_openLoop.js +45 -0
  257. package/src/tests/test_solidMetrics.js +58 -0
  258. package/src/tests/test_stlLoader.js +1889 -0
  259. package/src/tests/test_sweepFace.js +55 -0
  260. package/src/tests/test_tube.js +45 -0
  261. package/src/tests/test_tube_closedLoop.js +67 -0
  262. package/src/tests/tests.js +493 -0
  263. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  264. package/src/tools/dialogCapturePageFactory.js +227 -0
  265. package/src/tools/featureDialogCapturePage.js +47 -0
  266. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  267. package/src/utils/axisHelpers.js +99 -0
  268. package/src/utils/deepClone.js +69 -0
  269. package/src/utils/geometryTolerance.js +37 -0
  270. package/src/utils/normalizeTypeString.js +8 -0
  271. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,114 @@
1
+ import * as THREE from 'three';
2
+
3
+ const FACE_TYPES = new Set(['FACE', 'PLANE']);
4
+
5
+ function _isFaceType(obj) {
6
+ const type = String(obj?.type || '').toUpperCase();
7
+ return FACE_TYPES.has(type);
8
+ }
9
+
10
+ function _getScene(viewer) {
11
+ return viewer?.partHistory?.scene || viewer?.scene || null;
12
+ }
13
+
14
+ function _findSelectedFace(viewer) {
15
+ const scene = _getScene(viewer);
16
+ if (!scene) return null;
17
+
18
+ const last = viewer?._lastInspectorTarget || null;
19
+ if (last && _isFaceType(last) && last.selected === true) return last;
20
+
21
+ let found = null;
22
+ scene.traverse((obj) => {
23
+ if (found || !obj || !obj.selected) return;
24
+ if (_isFaceType(obj)) found = obj;
25
+ });
26
+ return found;
27
+ }
28
+
29
+ function _computeFaceCenter(obj) {
30
+ if (!obj) return null;
31
+ try { obj.updateMatrixWorld?.(true); } catch {}
32
+ try {
33
+ const box = new THREE.Box3().setFromObject(obj);
34
+ if (!box.isEmpty()) return box.getCenter(new THREE.Vector3());
35
+ } catch {}
36
+ try {
37
+ const geom = obj.geometry;
38
+ const bs = geom?.boundingSphere || (geom?.computeBoundingSphere && (geom.computeBoundingSphere(), geom.boundingSphere));
39
+ if (bs) return obj.localToWorld(bs.center.clone());
40
+ } catch {}
41
+ try {
42
+ return obj.getWorldPosition?.(new THREE.Vector3()) || null;
43
+ } catch {}
44
+ return null;
45
+ }
46
+
47
+ function _computeFaceNormal(obj) {
48
+ if (!obj) return null;
49
+ let n = null;
50
+ if (typeof obj.getAverageNormal === 'function') {
51
+ try { n = obj.getAverageNormal().clone(); } catch {}
52
+ }
53
+ if (!n || n.lengthSq() < 1e-10) {
54
+ try {
55
+ const q = obj.getWorldQuaternion?.(new THREE.Quaternion());
56
+ if (q) n = new THREE.Vector3(0, 0, 1).applyQuaternion(q);
57
+ } catch {}
58
+ }
59
+ if (!n || n.lengthSq() < 1e-10) return null;
60
+ return n.normalize();
61
+ }
62
+
63
+ function _orientCameraToFace(viewer, face) {
64
+ const cam = viewer?.camera;
65
+ if (!viewer || !cam || !face) return false;
66
+
67
+ const target = _computeFaceCenter(face);
68
+ const normal = _computeFaceNormal(face);
69
+ if (!target || !normal) return false;
70
+
71
+ const toCam = cam.position.clone().sub(target);
72
+ const dir = normal.dot(toCam) < 0 ? normal.clone().multiplyScalar(-1) : normal.clone();
73
+ dir.normalize();
74
+
75
+ const worldUp = new THREE.Vector3(0, 1, 0);
76
+ const ref = Math.abs(dir.dot(worldUp)) > 0.9 ? new THREE.Vector3(1, 0, 0) : worldUp;
77
+ const x = new THREE.Vector3().crossVectors(ref, dir).normalize();
78
+ const y = new THREE.Vector3().crossVectors(dir, x).normalize();
79
+
80
+ let dist = cam.position.distanceTo(target);
81
+ if (!Number.isFinite(dist) || dist < 1e-3) {
82
+ dist = Math.max(5, (viewer.viewSize || 10) * 1.5);
83
+ }
84
+
85
+ const nextPos = target.clone().add(dir.multiplyScalar(dist));
86
+ cam.position.copy(nextPos);
87
+ cam.up.copy(y);
88
+ cam.lookAt(target);
89
+ cam.updateMatrixWorld(true);
90
+
91
+ const controls = viewer.controls;
92
+ try { if (controls?.target) controls.target.copy(target); } catch {}
93
+ try { if (controls?._gizmos?.position) controls._gizmos.position.copy(target); } catch {}
94
+ try { controls?.update?.(); } catch {}
95
+ try { controls?.updateMatrixState?.(); } catch {}
96
+ try { viewer.render?.(); } catch {}
97
+
98
+ return true;
99
+ }
100
+
101
+ export function createOrientToFaceButton(viewer) {
102
+ const onClick = () => {
103
+ const face = _findSelectedFace(viewer);
104
+ if (!face) {
105
+ viewer?._toast?.('Select a face to orient the view.');
106
+ return;
107
+ }
108
+ if (!_orientCameraToFace(viewer, face)) {
109
+ viewer?._toast?.('Unable to orient view to that face.');
110
+ }
111
+ };
112
+
113
+ return { label: 'Perp', title: 'Orient view perpendicular to selected face', onClick };
114
+ }
@@ -0,0 +1,46 @@
1
+ // Registers the default toolbar buttons using the viewer's addToolbarButton API.
2
+ // Each button's logic is implemented in its own module.
3
+
4
+ import { createSaveButton } from './saveButton.js';
5
+ import { createUndoButton, createRedoButton } from './undoRedoButtons.js';
6
+ import { createZoomToFitButton } from './zoomToFitButton.js';
7
+ import { createOrientToFaceButton } from './orientToFaceButton.js';
8
+ import { createWireframeToggleButton } from './wireframeToggleButton.js';
9
+ import { createInspectorToggleButton } from './inspectorToggleButton.js';
10
+ import { createMetadataButton } from './metadataButton.js';
11
+ import { createImportButton } from './importButton.js';
12
+ import { createExportButton } from './exportButton.js';
13
+ import { createFlatPatternButton } from './flatPatternButton.js';
14
+ import { createAboutButton } from './aboutButton.js';
15
+ import { createTestsButton } from './testsButton.js';
16
+ import { createScriptRunnerButton } from './scriptRunnerButton.js';
17
+
18
+ export function registerDefaultToolbarButtons(viewer) {
19
+ if (!viewer || typeof viewer.addToolbarButton !== 'function') return;
20
+
21
+ const creators = [
22
+ createSaveButton,
23
+ createZoomToFitButton,
24
+ createOrientToFaceButton,
25
+ createWireframeToggleButton,
26
+ createInspectorToggleButton,
27
+ createMetadataButton,
28
+ createImportButton,
29
+ createExportButton,
30
+ createFlatPatternButton,
31
+ createAboutButton,
32
+ createTestsButton,
33
+ createScriptRunnerButton,
34
+ createUndoButton,
35
+ createRedoButton,
36
+ ];
37
+
38
+ for (const make of creators) {
39
+ try {
40
+ const spec = make(viewer);
41
+ if (!spec) continue;
42
+ const { label, title, onClick } = spec;
43
+ viewer.addToolbarButton(label, title, onClick);
44
+ } catch {}
45
+ }
46
+ }
@@ -0,0 +1,99 @@
1
+ import { generate3MF } from '../../exporters/threeMF.js';
2
+ import { localStorage as LS } from '../../idbStorage.js';
3
+ import * as THREE from 'three';
4
+
5
+ function _uint8ToBase64(uint8) {
6
+ let binary = '';
7
+ const chunk = 0x8000;
8
+ for (let i = 0; i < uint8.length; i += chunk) {
9
+ const sub = uint8.subarray(i, i + chunk);
10
+ binary += String.fromCharCode.apply(null, sub);
11
+ }
12
+ return btoa(binary);
13
+ }
14
+
15
+
16
+
17
+ export function createSaveButton(viewer) {
18
+ async function _captureThumbnail(size = 60) {
19
+ try {
20
+ const renderer = viewer?.renderer;
21
+ const canvas = renderer?.domElement;
22
+ const cam = viewer?.camera;
23
+ const controls = viewer?.controls;
24
+ if (!canvas || !cam) return null;
25
+
26
+ // Temporarily reorient exactly like clicking the ViewCube corner (top-front-right)
27
+ try {
28
+ const dir = new THREE.Vector3(1, 1, 1);
29
+ if (viewer?.viewCube && typeof viewer.viewCube._reorientCamera === 'function') {
30
+ viewer.viewCube._reorientCamera(dir, 'SAVE THUMBNAIL');
31
+ }
32
+ // Fit geometry within this oriented view
33
+ try { viewer.zoomToFit(1.1); } catch { }
34
+ } catch { /* ignore orientation failures */ }
35
+
36
+ // Ensure a fresh frame before capture
37
+ try { viewer.render(); } catch { }
38
+
39
+ // Wait one frame to be safe
40
+ await new Promise((resolve) => requestAnimationFrame(resolve));
41
+
42
+ const srcW = canvas.width || canvas.clientWidth || 1;
43
+ const srcH = canvas.height || canvas.clientHeight || 1;
44
+ const dst = document.createElement('canvas');
45
+ dst.width = size; dst.height = size;
46
+ const ctx = dst.getContext('2d');
47
+ if (!ctx) return null;
48
+ try { ctx.clearRect(0, 0, size, size); } catch { }
49
+ const scale = Math.min(size / srcW, size / srcH);
50
+ const dw = Math.max(1, Math.floor(srcW * scale));
51
+ const dh = Math.max(1, Math.floor(srcH * scale));
52
+ const dx = Math.floor((size - dw) / 2);
53
+ const dy = Math.floor((size - dh) / 2);
54
+ try { ctx.imageSmoothingEnabled = true; ctx.imageSmoothingQuality = 'high'; } catch { }
55
+ ctx.drawImage(canvas, 0, 0, srcW, srcH, dx, dy, dw, dh);
56
+ const dataUrl = dst.toDataURL('image/png');
57
+ return dataUrl;
58
+ } catch { return null; }
59
+ }
60
+ async function onClick() {
61
+ // Prefer the FileManagerWidget if present
62
+ try {
63
+ if (viewer?.fileManagerWidget?.saveCurrent) {
64
+ await viewer.fileManagerWidget.saveCurrent();
65
+ return;
66
+ }
67
+ } catch { }
68
+ // Fallback: quick autosave to IndexedDB storage
69
+ try {
70
+ // Produce a compact 3MF that embeds feature history (now includes PMI views) only
71
+ const json = await viewer?.partHistory?.toJSON?.();
72
+ let additionalFiles = undefined;
73
+ let modelMetadata = undefined;
74
+ try {
75
+ if (json && typeof json === 'string') {
76
+ additionalFiles = { 'Metadata/featureHistory.json': json };
77
+ modelMetadata = { featureHistoryPath: '/Metadata/featureHistory.json' };
78
+ }
79
+ const viewFiles = await viewer?.pmiViewsWidget?.captureViewImagesForPackage?.();
80
+ if (viewFiles && typeof viewFiles === 'object') {
81
+ additionalFiles = { ...(additionalFiles || {}), ...viewFiles };
82
+ }
83
+ } catch { }
84
+
85
+ const thumbnail = await _captureThumbnail(60);
86
+ const bytes = await generate3MF([], { unit: 'millimeter', precision: 6, scale: 1, additionalFiles, modelMetadata, thumbnail });
87
+ const b64 = _uint8ToBase64(bytes);
88
+ // Do not persist a separate thumbnail; it's embedded in the 3MF
89
+ const payload = { savedAt: new Date().toISOString(), data3mf: b64 };
90
+ LS.setItem('__BREP_MODEL__:autosave', JSON.stringify(payload));
91
+ LS.setItem('__BREP_MODELS_LASTNAME__', 'autosave');
92
+ alert('Saved as "autosave"');
93
+ } catch {
94
+ alert('Save failed.');
95
+ }
96
+ }
97
+
98
+ return { label: '💾', title: 'Save current model', onClick };
99
+ }
@@ -0,0 +1,302 @@
1
+ import { FloatingWindow } from '../FloatingWindow.js';
2
+ import '../EnvMonacoEditor.js';
3
+
4
+ const PANEL_KEY = Symbol('ScriptRunnerPanel');
5
+ const DEFAULT_SNIPPET = `// Access the app environment via the global "env" object.
6
+ console.log('env keys', Object.keys(env || {}));
7
+
8
+ // Example: log the active part history entry
9
+ console.log('active history', viewer?.partHistory?.getActiveStep?.());
10
+ `;
11
+
12
+ class ScriptRunnerPanel {
13
+ constructor(viewer) {
14
+ this.viewer = viewer;
15
+ this.window = null;
16
+ this.root = null;
17
+ this.editorEl = null;
18
+ this.editorWrap = null;
19
+ this.outputEl = null;
20
+ this.consoleWrap = null;
21
+ this.splitterEl = null;
22
+ this.contentRoot = null;
23
+ this.introEl = null;
24
+ this.statusEl = null;
25
+ this._initializedValue = false;
26
+ this._consoleHeight = 180;
27
+ this._lastContentRect = null;
28
+ }
29
+
30
+ toggle() {
31
+ if (this.root && this.root.style.display !== 'none') this.close();
32
+ else this.open();
33
+ }
34
+
35
+ open() {
36
+ this._ensureWindow();
37
+ if (!this.root) return;
38
+ this.root.style.display = 'flex';
39
+ try { this.editorEl?.refreshEnvAutocomplete?.(); } catch {}
40
+ }
41
+
42
+ close() {
43
+ if (!this.root) return;
44
+ try { this.root.style.display = 'none'; } catch {}
45
+ }
46
+
47
+ _ensureWindow() {
48
+ if (this.root) return;
49
+ const fw = new FloatingWindow({
50
+ title: 'Script Runner',
51
+ width: 760,
52
+ height: 520,
53
+ right: 16,
54
+ top: 56,
55
+ shaded: false,
56
+ onClose: () => this.close(),
57
+ });
58
+
59
+ const btnRun = document.createElement('button');
60
+ btnRun.className = 'fw-btn';
61
+ btnRun.textContent = 'Run';
62
+ btnRun.addEventListener('click', () => this._runCode());
63
+
64
+ const btnRefresh = document.createElement('button');
65
+ btnRefresh.className = 'fw-btn';
66
+ btnRefresh.textContent = 'Refresh env types';
67
+ btnRefresh.addEventListener('click', () => {
68
+ try {
69
+ this.editorEl?.refreshEnvAutocomplete?.();
70
+ this._setStatus('env typings refreshed from window.env');
71
+ } catch {
72
+ this._setStatus('Unable to refresh env typings');
73
+ }
74
+ });
75
+
76
+ fw.addHeaderAction(btnRun);
77
+ fw.addHeaderAction(btnRefresh);
78
+
79
+ const content = document.createElement('div');
80
+ content.style.display = 'flex';
81
+ content.style.flexDirection = 'column';
82
+ content.style.gap = '8px';
83
+ content.style.width = '100%';
84
+ content.style.height = '100%';
85
+ content.style.boxSizing = 'border-box';
86
+ content.style.minHeight = '0';
87
+ this.contentRoot = content;
88
+
89
+ const intro = document.createElement('div');
90
+ intro.textContent = 'Run ad-hoc JavaScript with Monaco autocomplete for window.env.';
91
+ intro.style.color = '#aeb6c5';
92
+ intro.style.font = '12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
93
+ intro.style.opacity = '0.9';
94
+ this.introEl = intro;
95
+
96
+ const editorWrap = document.createElement('div');
97
+ editorWrap.style.display = 'flex';
98
+ editorWrap.style.flexDirection = 'column';
99
+ editorWrap.style.flex = '1 1 60%';
100
+ editorWrap.style.minHeight = '200px';
101
+ editorWrap.style.minWidth = '0';
102
+ editorWrap.style.position = 'relative';
103
+
104
+ const editor = document.createElement('env-monaco-editor');
105
+ editor.style.position = 'absolute';
106
+ editor.style.inset = '0';
107
+ editor.setAttribute('language', 'javascript');
108
+ if (!this._initializedValue) {
109
+ editor.value = DEFAULT_SNIPPET;
110
+ this._initializedValue = true;
111
+ }
112
+
113
+ const splitter = document.createElement('div');
114
+ splitter.style.flex = '0 0 10px';
115
+ splitter.style.height = '10px';
116
+ splitter.style.cursor = 'row-resize';
117
+ splitter.style.background = 'linear-gradient(180deg, rgba(255,255,255,0.08), rgba(255,255,255,0.03))';
118
+ splitter.style.border = '1px solid #1f2530';
119
+ splitter.style.borderRadius = '8px';
120
+ splitter.style.margin = '6px 0';
121
+ splitter.style.position = 'relative';
122
+ splitter.style.zIndex = '2';
123
+ splitter.addEventListener('pointerdown', (ev) => this._onSplitterPointerDown(ev));
124
+
125
+ const consoleWrap = document.createElement('div');
126
+ consoleWrap.style.display = 'flex';
127
+ consoleWrap.style.flexDirection = 'column';
128
+ consoleWrap.style.flex = '0 0 180px';
129
+ consoleWrap.style.minHeight = '120px';
130
+ consoleWrap.style.maxHeight = '70vh';
131
+ consoleWrap.style.gap = '6px';
132
+ consoleWrap.style.position = 'relative';
133
+
134
+ const output = document.createElement('div');
135
+ output.style.flex = '1 1 auto';
136
+ output.style.background = '#0e1117';
137
+ output.style.border = '1px solid #1f2530';
138
+ output.style.borderRadius = '8px';
139
+ output.style.padding = '8px';
140
+ output.style.overflow = 'auto';
141
+ output.style.font = '12px/1.4 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
142
+ output.style.color = '#d1d5db';
143
+
144
+ const statusRow = document.createElement('div');
145
+ statusRow.style.display = 'flex';
146
+ statusRow.style.alignItems = 'center';
147
+ statusRow.style.gap = '8px';
148
+
149
+ const status = document.createElement('div');
150
+ status.style.flex = '1';
151
+ status.style.color = '#9ca3af';
152
+ status.style.font = '12px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
153
+ status.textContent = 'Idle';
154
+
155
+ const btnClear = document.createElement('button');
156
+ btnClear.className = 'fw-btn';
157
+ btnClear.textContent = 'Clear output';
158
+ btnClear.addEventListener('click', () => { output.innerHTML = ''; });
159
+
160
+ statusRow.appendChild(status);
161
+ statusRow.appendChild(btnClear);
162
+
163
+ content.appendChild(intro);
164
+ editorWrap.appendChild(editor);
165
+ content.appendChild(editorWrap);
166
+ content.appendChild(splitter);
167
+ consoleWrap.appendChild(statusRow);
168
+ consoleWrap.appendChild(output);
169
+ content.appendChild(consoleWrap);
170
+
171
+ fw.content.appendChild(content);
172
+
173
+ this.window = fw;
174
+ this.root = fw.root;
175
+ this.editorEl = editor;
176
+ this.editorWrap = editorWrap;
177
+ this.outputEl = output;
178
+ this.consoleWrap = consoleWrap;
179
+ this.splitterEl = splitter;
180
+ this.statusEl = status;
181
+ try { this.root.style.display = 'none'; } catch {}
182
+ requestAnimationFrame(() => this._applySplitHeights());
183
+ }
184
+
185
+ async _runCode() {
186
+ const code = this.editorEl?.value || '';
187
+ if (!code.trim()) {
188
+ this._setStatus('Nothing to run');
189
+ return;
190
+ }
191
+ this._appendOutput(`>>> Running at ${new Date().toLocaleTimeString()}`);
192
+ this._setStatus('Running...');
193
+
194
+ const exec = () => {
195
+ const fn = new Function('viewer', 'env', 'monaco', `"use strict";\n${code}`);
196
+ return fn(this.viewer, window.env, window.monaco);
197
+ };
198
+
199
+ try {
200
+ const result = exec();
201
+ if (result instanceof Promise) {
202
+ const resolved = await result;
203
+ this._appendOutput(this._stringify(resolved));
204
+ this._setStatus('Completed (async)');
205
+ } else {
206
+ this._appendOutput(this._stringify(result));
207
+ this._setStatus('Completed');
208
+ }
209
+ } catch (e) {
210
+ const msg = e?.stack || e?.message || String(e);
211
+ this._appendOutput(msg, true);
212
+ this._setStatus('Error');
213
+ try { console.error('[ScriptRunner]', e); } catch {}
214
+ }
215
+ }
216
+
217
+ _appendOutput(text, isError = false) {
218
+ if (!this.outputEl) return;
219
+ const line = document.createElement('div');
220
+ line.textContent = text;
221
+ line.style.whiteSpace = 'pre-wrap';
222
+ line.style.color = isError ? '#fca5a5' : '#d1d5db';
223
+ this.outputEl.appendChild(line);
224
+ this.outputEl.scrollTop = this.outputEl.scrollHeight;
225
+ }
226
+
227
+ _setStatus(msg) {
228
+ if (!this.statusEl) return;
229
+ this.statusEl.textContent = msg;
230
+ }
231
+
232
+ _stringify(value) {
233
+ try {
234
+ if (typeof value === 'string') return value;
235
+ if (typeof value === 'function') return value.toString();
236
+ if (value === undefined) return 'undefined';
237
+ if (value === null) return 'null';
238
+ const json = JSON.stringify(value, null, 2);
239
+ return typeof json === 'string' ? json : String(value);
240
+ } catch {
241
+ try { return String(value); } catch { return '[unprintable]'; }
242
+ }
243
+ }
244
+
245
+ _onSplitterPointerDown(ev) {
246
+ if (ev.button !== 0) return;
247
+ const startY = ev.clientY;
248
+ const startHeight = (this.consoleWrap?.getBoundingClientRect?.().height) || this._consoleHeight;
249
+ const onMove = (e) => {
250
+ const dy = e.clientY - startY;
251
+ this._setConsoleHeight(startHeight - dy);
252
+ try { e.preventDefault(); } catch {}
253
+ };
254
+ const onUp = (e) => {
255
+ window.removeEventListener('pointermove', onMove, true);
256
+ window.removeEventListener('pointerup', onUp, true);
257
+ try { this._consoleHeight = (this.consoleWrap?.getBoundingClientRect?.().height) || this._consoleHeight; } catch {}
258
+ };
259
+ window.addEventListener('pointermove', onMove, true);
260
+ window.addEventListener('pointerup', onUp, true);
261
+ try { ev.preventDefault(); } catch {}
262
+ }
263
+
264
+ _setConsoleHeight(px) {
265
+ if (!this.contentRoot || !this.consoleWrap || !this.editorWrap) return;
266
+ const minConsole = 120;
267
+ const minEditor = 180;
268
+ const rect = this.contentRoot.getBoundingClientRect?.();
269
+ const contentH = rect?.height || 0;
270
+ const introH = this.introEl?.offsetHeight || 0;
271
+ const splitterH = this.splitterEl?.offsetHeight || 10;
272
+ const available = Math.max(0, contentH - introH - splitterH - 8); // account for gap/margins
273
+ if (available <= 0) return;
274
+ const maxConsole = Math.max(minConsole, available - minEditor);
275
+ const clamped = Math.min(Math.max(px, minConsole), maxConsole);
276
+ const editorHeight = Math.max(minEditor, available - clamped);
277
+ this._consoleHeight = clamped;
278
+ this.consoleWrap.style.flexBasis = `${clamped}px`;
279
+ this.consoleWrap.style.height = `${clamped}px`;
280
+ this.editorWrap.style.flexBasis = `${editorHeight}px`;
281
+ this.editorWrap.style.height = `${editorHeight}px`;
282
+ try { this.editorEl?.editor?.layout?.(); } catch {}
283
+ }
284
+
285
+ _applySplitHeights() {
286
+ this._setConsoleHeight(this._consoleHeight);
287
+ try { this.editorEl?.editor?.layout?.(); } catch {}
288
+ }
289
+ }
290
+
291
+ export function createScriptRunnerButton(viewer) {
292
+ if (!viewer) return null;
293
+ if (!viewer[PANEL_KEY]) {
294
+ viewer[PANEL_KEY] = new ScriptRunnerPanel(viewer);
295
+ }
296
+ const panel = viewer[PANEL_KEY];
297
+ return {
298
+ label: '</>',
299
+ title: 'Open Script Runner',
300
+ onClick: () => panel.toggle(),
301
+ };
302
+ }
@@ -0,0 +1,26 @@
1
+ // Adds a "tests" toolbar button that opens the Browser Testing window on demand.
2
+
3
+ export function createTestsButton(viewer) {
4
+ if (!viewer) return null;
5
+
6
+ let tester = null;
7
+
8
+ return {
9
+ label: 'tests',
10
+ title: 'Open browser tests',
11
+ onClick: async () => {
12
+ try {
13
+ if (!tester) {
14
+ const mod = await import('../../tests/browserTests.js');
15
+ const { BrowserTesting } = mod || {};
16
+ if (typeof BrowserTesting !== 'function') return;
17
+ tester = new BrowserTesting({ viewer });
18
+ } else {
19
+ tester.toggle?.();
20
+ }
21
+ } catch (e) {
22
+ try { console.warn('Failed to open Browser Testing:', e); } catch {}
23
+ }
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,25 @@
1
+ export function createUndoButton(viewer) {
2
+ const onClick = () => {
3
+ try {
4
+ if (typeof viewer?._runFeatureHistoryUndoRedo === 'function') {
5
+ viewer._runFeatureHistoryUndoRedo('undo');
6
+ } else {
7
+ viewer?.partHistory?.undoFeatureHistory?.();
8
+ }
9
+ } catch { }
10
+ };
11
+ return { label: '↶', title: 'Undo feature history (Ctrl+Z)', onClick };
12
+ }
13
+
14
+ export function createRedoButton(viewer) {
15
+ const onClick = () => {
16
+ try {
17
+ if (typeof viewer?._runFeatureHistoryUndoRedo === 'function') {
18
+ viewer._runFeatureHistoryUndoRedo('redo');
19
+ } else {
20
+ viewer?.partHistory?.redoFeatureHistory?.();
21
+ }
22
+ } catch { }
23
+ };
24
+ return { label: '↷', title: 'Redo feature history (Ctrl+Y)', onClick };
25
+ }
@@ -0,0 +1,5 @@
1
+ export function createWireframeToggleButton(viewer) {
2
+ const onClick = () => { try { viewer?.toggleWireframe?.(); } catch {} };
3
+ return { label: '🕸️', title: 'Toggle wireframe', onClick };
4
+ }
5
+
@@ -0,0 +1,5 @@
1
+ export function createZoomToFitButton(viewer) {
2
+ const onClick = () => { try { viewer?.zoomToFit?.(); } catch {} };
3
+ return { label: '🔍', title: 'Frame all geometry', onClick };
4
+ }
5
+