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,597 @@
1
+ // browserTests.js - BrowserTesting with UI widget (dark mode, top-right)
2
+
3
+ import { Viewer } from '../UI/viewer.js';
4
+ import { FloatingWindow } from '../UI/FloatingWindow.js';
5
+ import { runTests, testFunctions, runSingleTest } from './tests.js';
6
+ import { ConsoleCapture } from './ConsoleCapture.js'
7
+
8
+
9
+
10
+
11
+
12
+ export class BrowserTesting {
13
+ constructor({
14
+ containerEl = document.getElementById('viewport'),
15
+ sidebarEl = document.getElementById('sidebar'),
16
+ exposeEnvOnWindow = true,
17
+ viewer = null, // optional: reuse existing Viewer instance if provided
18
+ } = {}) {
19
+ // URL flag to auto-progress (kept from previous behavior)
20
+ this.autoProgress = window.location.href.includes("autoNext=true");
21
+
22
+ // Initialize Viewer env (reuse provided viewer when available)
23
+ this.env = viewer instanceof Viewer ? viewer : new Viewer({ container: containerEl, sidebar: sidebarEl });
24
+ if (exposeEnvOnWindow) window.env = this.env;
25
+
26
+ // Test registry (names in stable order)
27
+ this.testNames = testFunctions.map(func => func.test.name);
28
+ //console.log(testFunctions, this.testNames);
29
+
30
+ this.currentIndex = 0;
31
+
32
+ // Per-test runtime state
33
+ this.enabled = new Map(this.testNames.map(n => [n, true]));
34
+ this.status = new Map(this.testNames.map(n => [n, ""])); // "", "pass", "fail"
35
+ this.errors = new Map(); // name -> { message, stack } captured on failure
36
+ // Per-test canvas snapshot
37
+ this.screenshots = new Map(); // name -> dataURL
38
+ this.logWindow = null;
39
+ this.logContent = null;
40
+
41
+ // Popup container for screenshots between tests (kept from previous behavior)
42
+ this.popupDiv = document.createElement("div");
43
+ this.popupDiv.style.padding = "10px";
44
+ this.popupDiv.style.background = "#0b0b0e";
45
+ this.popupDiv.style.color = "#e5e7eb";
46
+ this.popupDiv.style.border = "1px solid #2a2a33";
47
+ this.popupDiv.style.borderRadius = "12px";
48
+ this.popupDiv.style.maxWidth = "90vw";
49
+ this.popupDiv.style.maxHeight = "100%";
50
+ this.popupDiv.style.overflow = "auto";
51
+
52
+ // Build the UI widget
53
+ this.ui = this._buildUI();
54
+
55
+ // Optional global for debugging
56
+ window.browserTesting = this;
57
+ if (this.autoProgress) {
58
+
59
+
60
+ this.loggingTool = new ConsoleCapture({ captureStack: false });
61
+ this.loggingTool.install();
62
+
63
+ }
64
+ }
65
+
66
+ // ====== Small helpers ======
67
+ sleep(ms) { return new Promise(res => setTimeout(res, ms)); }
68
+
69
+ // ====== Screenshot dump (unchanged behavior) ======
70
+ async dumpScreenshot() {
71
+ await this.sleep(2000);
72
+ const image = this.env.renderer.domElement.toDataURL();
73
+ return image;
74
+ }
75
+
76
+ // ====== Visibility helpers ======
77
+ show() { try { if (this.ui?.window?.root) this.ui.window.root.style.display = ''; } catch {} }
78
+ hide() { try { if (this.ui?.window?.root) this.ui.window.root.style.display = 'none'; } catch {} }
79
+ toggle() {
80
+ try {
81
+ if (!this.ui?.window?.root) return;
82
+ const cur = this.ui.window.root.style.display;
83
+ this.ui.window.root.style.display = (cur === 'none') ? '' : 'none';
84
+ } catch {}
85
+ }
86
+
87
+ // ====== Hook invoked between tests (kept & extended) ======
88
+ async callBetweenTestsToRender(featureHistory, isLastTest) {
89
+ // Keep parity with original assignments
90
+ this.env.partHistory.features = featureHistory.features;
91
+ this.env.scene = featureHistory.scene;
92
+
93
+ try {
94
+ if (this.autoProgress) await this.sleep(1000);
95
+ await this.sleep(1000);
96
+ await this.env.renderer.render(this.env.scene, this.env.camera);
97
+
98
+ // capture screenshot to the popupDiv
99
+ const image = this.env.renderer.domElement.toDataURL();
100
+ const img = document.createElement("img");
101
+ img.src = image;
102
+ img.style.maxWidth = "360px";
103
+ img.style.height = "auto";
104
+ img.style.display = "block";
105
+ img.style.margin = "8px 0";
106
+ this.popupDiv.appendChild(img);
107
+ } catch (error) {
108
+ console.log("Error occurred while writing to popup:", error);
109
+ }
110
+
111
+ if (this.autoProgress, !isLastTest) {
112
+ const popup = window.open("", "_blank");
113
+ if (popup && popup.document && popup.document.body) {
114
+ popup.document.body.style.background = "#0b0b0e";
115
+ popup.document.body.style.color = "#e5e7eb";
116
+ popup.document.body.appendChild(this.popupDiv);
117
+
118
+ }
119
+ }
120
+ }
121
+
122
+ // ====== PUBLIC: run all tests via external test harness (legacy entry) ======
123
+ async run() {
124
+ // Preserve old entry point, now wired through the UI "Run All" anyway
125
+ await runTests(this.env.partHistory, this.callBetweenTestsToRender.bind(this));
126
+ }
127
+
128
+ // ====== UI: Build top-right widget ======
129
+ _buildUI() {
130
+ // Floating window container (draggable, shade-on-title, resizable)
131
+ const fw = new FloatingWindow({ title: 'Browser Testing', width: 500, height: 700, right: 16, top: 40, shaded: false });
132
+
133
+ // Controls row
134
+ const controls = document.createElement("div");
135
+ Object.assign(controls.style, {
136
+ padding: "10px 12px",
137
+ display: "grid",
138
+ gridTemplateColumns: "repeat(4, 1fr)",
139
+ gap: "8px",
140
+ borderBottom: "1px solid #23232b",
141
+ });
142
+
143
+ const btnRunCurrent = makeButton("Run (current)");
144
+ const btnPrev = makeButton("Previous");
145
+ const btnNext = makeButton("Next");
146
+ const btnRunAll = makeButton("Run All");
147
+
148
+ controls.appendChild(btnRunCurrent);
149
+ controls.appendChild(btnPrev);
150
+ controls.appendChild(btnNext);
151
+ controls.appendChild(btnRunAll);
152
+
153
+ // Table container (content area already scrolls; this keeps structure)
154
+ const tableWrap = document.createElement("div");
155
+ // Object.assign(tableWrap.style, {
156
+ // overflow: "auto",
157
+ // maxHeight: "100%",
158
+ // });
159
+
160
+ // Table
161
+ const table = document.createElement("table");
162
+ Object.assign(table.style, {
163
+ width: "100%",
164
+ borderCollapse: "collapse",
165
+ });
166
+
167
+ // THEAD
168
+ const thead = document.createElement("thead");
169
+ const headRow = document.createElement("tr");
170
+ headRow.appendChild(th("Test (enable/disable)", "50%"));
171
+ headRow.appendChild(th("Status", "20%"));
172
+ headRow.appendChild(th("Actions", "30%"));
173
+ thead.appendChild(headRow);
174
+ table.appendChild(thead);
175
+
176
+ // TBODY (rows per test)
177
+ const tbody = document.createElement("tbody");
178
+ this._rowRefs = new Map(); // name -> { row, checkbox, statusCell, runBtn, logBtn }
179
+ this.testNames.forEach((name, idx) => {
180
+ const row = document.createElement("tr");
181
+ Object.assign(row.style, rowStyle());
182
+
183
+ // col 1: checkbox + label
184
+ const c1 = document.createElement("td");
185
+ c1.style.height = "10px";
186
+ Object.assign(c1.style, cellStyle());
187
+ const label = document.createElement("label");
188
+ label.style.display = "flex";
189
+ label.style.alignItems = "center";
190
+ label.style.gap = "8px";
191
+ const cb = document.createElement("input");
192
+ cb.type = "checkbox";
193
+ cb.checked = true;
194
+ cb.addEventListener("change", () => {
195
+ this.enabled.set(name, cb.checked);
196
+ });
197
+ const text = document.createElement("span");
198
+ text.textContent = name;
199
+ label.appendChild(cb);
200
+ label.appendChild(text);
201
+ c1.appendChild(label);
202
+
203
+ // col 2: status
204
+ const c2 = document.createElement("td");
205
+ Object.assign(c2.style, cellStyle());
206
+ updateStatusCell(c2, this.status.get(name));
207
+
208
+ // col 3: actions
209
+ const c3 = document.createElement("td");
210
+ Object.assign(c3.style, cellStyle());
211
+ c3.style.display = "flex";
212
+ c3.style.gap = "6px";
213
+ const runBtn = miniButton("▷");
214
+ const logBtn = miniButton("Show Log");
215
+ c3.appendChild(runBtn);
216
+ c3.appendChild(logBtn);
217
+
218
+ // row events
219
+ row.addEventListener("click", (e) => {
220
+ // Don't change selection if clicking a control that handles its own action
221
+ const tag = (e.target && e.target.tagName) ? e.target.tagName.toLowerCase() : "";
222
+ if (tag === "button" || tag === "input" || tag === "label") return;
223
+ this._selectRow(idx);
224
+ });
225
+
226
+ // hook up actions
227
+ runBtn.addEventListener("click", async (e) => {
228
+ e.stopPropagation();
229
+ this._selectRow(idx);
230
+ await this._runSingleByName(name);
231
+ });
232
+ logBtn.addEventListener("click", (e) => {
233
+ e.stopPropagation();
234
+ this._showErrorLog(name);
235
+ });
236
+
237
+ // assemble row
238
+ row.appendChild(c1);
239
+ row.appendChild(c2);
240
+ row.appendChild(c3);
241
+ tbody.appendChild(row);
242
+
243
+ this._rowRefs.set(name, { row, checkbox: cb, statusCell: c2, runBtn, logBtn });
244
+ });
245
+
246
+ table.appendChild(tbody);
247
+ tableWrap.appendChild(table);
248
+
249
+ // Footer: current selection
250
+ const footer = document.createElement("div");
251
+ Object.assign(footer.style, {
252
+ padding: "10px 12px",
253
+ borderTop: "1px solid #23232b",
254
+ color: "#a7aab3",
255
+ display: "flex",
256
+ justifyContent: "space-between",
257
+ });
258
+ this._selectionLabel = document.createElement("span");
259
+ this._selectionLabel.textContent = this._currentLabel();
260
+ footer.appendChild(this._selectionLabel);
261
+
262
+ // Assemble widget
263
+ fw.content.appendChild(controls);
264
+ fw.content.appendChild(tableWrap);
265
+ fw.content.appendChild(footer);
266
+
267
+ // Wire top buttons
268
+ btnRunCurrent.addEventListener("click", async () => {
269
+ const name = this.testNames[this.currentIndex];
270
+ await this._runSingleByName(name);
271
+ });
272
+ btnPrev.addEventListener("click", () => this._moveSelection(-1));
273
+ btnNext.addEventListener("click", () => this._moveSelection(+1));
274
+ btnRunAll.addEventListener("click", async () => {
275
+ await this._runAllEnabled();
276
+ });
277
+
278
+ // Initial selected row styling
279
+ this._applySelectionStyles();
280
+
281
+ // Start shaded/collapsed if desired by default; keep behavior off by default
282
+ // fw.setShaded(true);
283
+
284
+ return { window: fw, controls, table, tbody };
285
+ }
286
+
287
+ // ====== Selection helpers ======
288
+ _currentLabel() {
289
+ const name = this.testNames[this.currentIndex] || "(none)";
290
+ return `Selected: ${name} (${this.currentIndex + 1}/${this.testNames.length})`;
291
+ }
292
+
293
+ _moveSelection(delta) {
294
+ if (!this.testNames.length) return;
295
+ this.currentIndex = (this.currentIndex + delta + this.testNames.length) % this.testNames.length;
296
+ this._applySelectionStyles();
297
+ }
298
+
299
+ _selectRow(idx) {
300
+ if (idx < 0 || idx >= this.testNames.length) return;
301
+ this.currentIndex = idx;
302
+ this._applySelectionStyles();
303
+ }
304
+
305
+ _applySelectionStyles() {
306
+ this._selectionLabel.textContent = this._currentLabel();
307
+ this.testNames.forEach((name, idx) => {
308
+ const ref = this._rowRefs.get(name);
309
+ if (!ref) return;
310
+ if (idx === this.currentIndex) {
311
+ ref.row.style.outline = "2px solid #4155ff88";
312
+ ref.row.style.background = "#12131a";
313
+ } else {
314
+ ref.row.style.outline = "none";
315
+ ref.row.style.background = "transparent";
316
+ }
317
+ });
318
+ }
319
+
320
+ // ====== Status + error helpers ======
321
+ _setStatus(name, value /* "", "pass", "fail" */) {
322
+ this.status.set(name, value);
323
+ const ref = this._rowRefs.get(name);
324
+ if (ref) updateStatusCell(ref.statusCell, value);
325
+ }
326
+
327
+ _setError(name, error) {
328
+ if (!error) { this.errors.delete(name); return; }
329
+ this.errors.set(name, {
330
+ message: String(error && error.message ? error.message : error),
331
+ stack: (error && error.stack) ? String(error.stack) : "",
332
+ });
333
+ }
334
+
335
+ _showErrorLog(name) {
336
+ const err = this.errors.get(name);
337
+ this._ensureLogWindow();
338
+ if (!this.logWindow || !this.logContent) return;
339
+ this.logWindow.setTitle(`Log - ${name}`);
340
+ this.logContent.innerHTML = "";
341
+
342
+ const screenshot = this.screenshots.get(name);
343
+ if (screenshot) {
344
+ const shotWrap = document.createElement("div");
345
+ Object.assign(shotWrap.style, {
346
+ marginBottom: "8px",
347
+ display: "flex",
348
+ justifyContent: "center",
349
+ });
350
+ const img = document.createElement("img");
351
+ img.src = screenshot;
352
+ img.alt = `Canvas snapshot for ${name}`;
353
+ img.style.maxWidth = "100%";
354
+ img.style.height = "auto";
355
+ img.style.border = "1px solid #1e2030";
356
+ img.style.borderRadius = "8px";
357
+ shotWrap.appendChild(img);
358
+ this.logContent.appendChild(shotWrap);
359
+ }
360
+
361
+ const pre = document.createElement("pre");
362
+ pre.style.whiteSpace = "pre-wrap";
363
+ pre.style.lineHeight = "1.5";
364
+ pre.style.background = "#111217";
365
+ pre.style.border = "1px solid #1e2030";
366
+ pre.style.padding = "3px";
367
+ pre.style.borderRadius = "8px";
368
+ pre.textContent = err ? `${err.message}\n\n${err.stack}` : "No error captured for this test.";
369
+ this.logContent.appendChild(pre);
370
+ this.logWindow.root.style.display = "flex";
371
+ }
372
+
373
+ _ensureLogWindow() {
374
+ if (this.logWindow) return;
375
+ const fw = new FloatingWindow({
376
+ title: "Log",
377
+ width: 720,
378
+ height: 520,
379
+ right: 24,
380
+ top: 80,
381
+ shaded: false,
382
+ onClose: () => this._hideLogWindow(),
383
+ });
384
+
385
+ const content = document.createElement("div");
386
+ content.style.display = "flex";
387
+ content.style.flexDirection = "column";
388
+ content.style.gap = "10px";
389
+ content.style.width = "100%";
390
+ content.style.height = "100%";
391
+ content.style.boxSizing = "border-box";
392
+
393
+ fw.content.appendChild(content);
394
+
395
+ this.logWindow = fw;
396
+ this.logContent = content;
397
+ try { fw.root.style.display = "none"; } catch {}
398
+ }
399
+
400
+ _hideLogWindow() {
401
+ if (!this.logWindow?.root) return;
402
+ try { this.logWindow.root.style.display = "none"; } catch {}
403
+ }
404
+
405
+ // ====== Execution helpers ======
406
+ async _runSingleByName(name) {
407
+ // clear previous error for this test
408
+ this._setError(name, null);
409
+ this._setStatus(name, "");
410
+
411
+ // Visually mark as running
412
+ const ref = this._rowRefs.get(name);
413
+ if (ref) {
414
+ ref.row.style.background = "#151726";
415
+ }
416
+
417
+ const functionToRun = testFunctions.find(func => func.test.name === name);
418
+
419
+ try {
420
+ // Try a single-test runner if provided; otherwise fall back to calling the function directly.
421
+ if (typeof runSingleTest === "function") {
422
+ this.startLogging();
423
+ this.env.partHistory.reset();
424
+ await runSingleTest(functionToRun, this.env.partHistory);
425
+ // After the test completes, zoom-to-fit then capture a canvas snapshot for the log
426
+ try {
427
+ if (typeof this.env.zoomToFit === 'function') {
428
+ this.env.zoomToFit(1.2);
429
+ } else {
430
+ this.env.render();
431
+ }
432
+ // Wait a frame to ensure the camera/render settled
433
+ await new Promise((resolve) => requestAnimationFrame(resolve));
434
+ const dataURL = this.env.renderer?.domElement?.toDataURL?.("image/png");
435
+ if (dataURL && typeof dataURL === 'string' && dataURL.startsWith('data:image')) {
436
+ this.screenshots.set(name, dataURL);
437
+ }
438
+ } catch (_) { /* best-effort snapshot */ }
439
+
440
+ this._setError(name, await this.endLogging());
441
+ }
442
+ } catch (err) {
443
+ console.error(`Error in test ${name}:`, err);
444
+ this._setError(name, err);
445
+ this._setStatus(name, "fail");
446
+ } finally {
447
+ // restore background depending on selection
448
+ const idx = this.testNames.indexOf(name);
449
+ if (ref) {
450
+ if (idx === this.currentIndex) {
451
+ ref.row.style.background = "#12131a";
452
+ } else {
453
+ ref.row.style.background = "transparent";
454
+ }
455
+ }
456
+ }
457
+ }
458
+
459
+ async startLogging() {
460
+ if (!this.loggingTool) return;
461
+ this.loggingTool.clearLogs();
462
+
463
+ }
464
+ async endLogging() {
465
+ if (!this.loggingTool) return "";
466
+ let errorString = "";
467
+ this.loggingTool.getLogs().forEach(log => {
468
+ console.log(log);
469
+
470
+ log.args.forEach(arg => {
471
+ // test if log.args is a string and then move on to the next loop
472
+ if (typeof arg === "string") {
473
+ errorString += `${arg}\n`;
474
+ return;
475
+ }
476
+ if (typeof arg === "object" && arg !== null) {
477
+ errorString += `${JSON.stringify(arg, null, 2)}\n`;
478
+ }
479
+ });
480
+
481
+ });
482
+ return errorString;
483
+ }
484
+
485
+
486
+ async _runAllEnabled() {
487
+ // Reset quick screenshot board
488
+ this.popupDiv.innerHTML = "";
489
+
490
+ for (let i = 0; i < this.testNames.length; i++) {
491
+ const name = this.testNames[i];
492
+ if (!this.enabled.get(name)) continue;
493
+ this._selectRow(i);
494
+ /* eslint-disable no-await-in-loop */
495
+ await this._runSingleByName(name);
496
+ /* eslint-enable no-await-in-loop */
497
+ }
498
+ }
499
+ }
500
+
501
+ // ====== Styling helpers ======
502
+ function darkButtonStyle() {
503
+ return {
504
+ background: "#1f2937",
505
+ color: "#f9fafb",
506
+ border: "1px solid #374151",
507
+ padding: "3px",
508
+ borderRadius: "8px",
509
+ cursor: "pointer",
510
+ fontWeight: "700",
511
+ outline: "none",
512
+ transition: "background 120ms ease, transform 60ms ease, box-shadow 120ms ease",
513
+ userSelect: "none",
514
+ };
515
+ }
516
+ function decorateButtonHover(btn) {
517
+ btn.addEventListener("mouseenter", () => {
518
+ btn.style.background = "#2b3545";
519
+ btn.style.boxShadow = "0 3px 10px rgba(0,0,0,0.35)";
520
+ });
521
+ btn.addEventListener("mouseleave", () => {
522
+ btn.style.background = "#1f2937";
523
+ btn.style.transform = "none";
524
+ btn.style.boxShadow = "none";
525
+ });
526
+ btn.addEventListener("mousedown", () => {
527
+ btn.style.transform = "translateY(1px)";
528
+ });
529
+ btn.addEventListener("mouseup", () => {
530
+ btn.style.transform = "none";
531
+ });
532
+ }
533
+ function makeButton(label) {
534
+ const btn = document.createElement("button");
535
+ btn.type = "button";
536
+ btn.textContent = label;
537
+ Object.assign(btn.style, darkButtonStyle());
538
+ decorateButtonHover(btn);
539
+ return btn;
540
+ }
541
+ function miniButton(label) {
542
+ const btn = makeButton(label);
543
+ btn.style.padding = "3px";
544
+ btn.style.borderRadius = "6px";
545
+ btn.style.fontSize = "10px";
546
+ return btn;
547
+ }
548
+ function th(text, width) {
549
+ const th = document.createElement("th");
550
+ th.textContent = text;
551
+ th.style.textAlign = "left";
552
+ th.style.padding = "10px 12px";
553
+ th.style.borderBottom = "1px solid #23232b";
554
+ th.style.color = "#9aa0aa";
555
+ th.style.fontWeight = "600";
556
+ if (width) th.style.width = width;
557
+ return th;
558
+ }
559
+ function rowStyle() {
560
+ return {
561
+ borderBottom: "1px solid #1c1d25",
562
+ };
563
+ }
564
+ function cellStyle() {
565
+ return {
566
+ padding: "3px 12px",
567
+ verticalAlign: "middle",
568
+
569
+ };
570
+ }
571
+ function updateStatusCell(cell, value) {
572
+ // value: "", "pass", "fail"
573
+ cell.textContent = "";
574
+ const badge = document.createElement("span");
575
+ badge.textContent = value === "" ? "" : value.toUpperCase();
576
+ badge.style.fontWeight = "800";
577
+ badge.style.letterSpacing = "0.5px";
578
+ badge.style.padding = value ? "2px 8px" : "0";
579
+ badge.style.borderRadius = "999px";
580
+ if (value === "pass") {
581
+ badge.style.background = "#093d2a";
582
+ badge.style.color = "#86efac";
583
+ badge.style.border = "1px solid #14532d";
584
+ } else if (value === "fail") {
585
+ badge.style.background = "#3a0b0f";
586
+ badge.style.color = "#fca5a5";
587
+ badge.style.border = "1px solid #7f1d1d";
588
+ } else {
589
+ badge.style.background = "transparent";
590
+ badge.style.color = "#a7aab3";
591
+ }
592
+ cell.appendChild(badge);
593
+ }
594
+
595
+
596
+ // Note: No default instance is created here.
597
+ // The BrowserTesting UI is now launched on-demand from a toolbar button.