brep-io-kernel 1.0.0-ci.9

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 +154 -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,240 @@
1
+ import { listComponentRecords, getComponentRecord, extractThumbnailFrom3MFBase64 } from '../services/componentLibrary.js';
2
+ import { ModelLibraryView } from './ModelLibraryView.js';
3
+
4
+ export function openComponentSelectorModal({ title = 'Select Component' } = {}) {
5
+ return new Promise((resolve) => {
6
+ const records = listComponentRecords();
7
+
8
+ const overlay = document.createElement('div');
9
+ overlay.className = 'component-selector-overlay';
10
+
11
+ const panel = document.createElement('div');
12
+ panel.className = 'component-selector-panel';
13
+
14
+ const header = document.createElement('div');
15
+ header.className = 'cs-header';
16
+ header.textContent = title;
17
+
18
+ const controls = document.createElement('div');
19
+ controls.className = 'cs-controls';
20
+
21
+ const search = document.createElement('input');
22
+ search.type = 'search';
23
+ search.placeholder = 'Search components…';
24
+ search.className = 'cs-search';
25
+ controls.appendChild(search);
26
+
27
+ let iconsOnly = false;
28
+ const viewToggle = document.createElement('button');
29
+ viewToggle.type = 'button';
30
+ viewToggle.className = 'fm-btn cs-view-toggle';
31
+ controls.appendChild(viewToggle);
32
+
33
+ const list = document.createElement('div');
34
+ list.className = 'cs-list';
35
+
36
+ const footer = document.createElement('div');
37
+ footer.className = 'cs-footer';
38
+ const cancel = document.createElement('button');
39
+ cancel.type = 'button';
40
+ cancel.textContent = 'Cancel';
41
+ cancel.className = 'cs-btn';
42
+ footer.appendChild(cancel);
43
+
44
+ panel.appendChild(header);
45
+ panel.appendChild(controls);
46
+ panel.appendChild(list);
47
+ panel.appendChild(footer);
48
+ overlay.appendChild(panel);
49
+ document.body.appendChild(overlay);
50
+
51
+ const cleanup = (result) => {
52
+ try { document.body.removeChild(overlay); } catch {}
53
+ resolve(result);
54
+ };
55
+
56
+ cancel.addEventListener('click', () => cleanup(null));
57
+ overlay.addEventListener('click', (ev) => {
58
+ if (ev.target === overlay) cleanup(null);
59
+ });
60
+
61
+ const thumbCache = new Map();
62
+ const gallery = new ModelLibraryView({
63
+ container: list,
64
+ iconsOnly,
65
+ allowDelete: false,
66
+ showOpenButton: false,
67
+ loadThumbnail: async (item, imgEl) => {
68
+ if (!item || !imgEl) return;
69
+ if (item.thumbnail) {
70
+ imgEl.src = item.thumbnail;
71
+ thumbCache.set(item.name, item.thumbnail);
72
+ return;
73
+ }
74
+ if (!item.data3mf) {
75
+ if (thumbCache.has(item.name)) {
76
+ const cached = thumbCache.get(item.name);
77
+ if (cached) imgEl.src = cached;
78
+ }
79
+ return;
80
+ }
81
+ if (thumbCache.has(item.name)) {
82
+ const cached = thumbCache.get(item.name);
83
+ if (cached) {
84
+ imgEl.src = cached;
85
+ return;
86
+ }
87
+ }
88
+ const src = await extractThumbnailFrom3MFBase64(item.data3mf);
89
+ if (src) {
90
+ thumbCache.set(item.name, src);
91
+ imgEl.src = src;
92
+ }
93
+ },
94
+ onOpen: async (name) => {
95
+ const record = getComponentRecord(name);
96
+ if (!record || !record.data3mf) {
97
+ cleanup(null);
98
+ return;
99
+ }
100
+ cleanup(record);
101
+ },
102
+ emptyMessage: records.length ? 'No components match the search.' : 'No stored components found.',
103
+ });
104
+
105
+ const updateViewToggle = () => {
106
+ if (!viewToggle) return;
107
+ if (iconsOnly) {
108
+ viewToggle.textContent = '☰';
109
+ viewToggle.title = 'Switch to list view';
110
+ } else {
111
+ viewToggle.textContent = '🔳';
112
+ viewToggle.title = 'Switch to grid view';
113
+ }
114
+ };
115
+ updateViewToggle();
116
+
117
+ const render = () => {
118
+ const term = (search.value || '').trim().toLowerCase();
119
+ const matches = !term
120
+ ? records
121
+ : records.filter((rec) => rec.name.toLowerCase().includes(term));
122
+ const mapped = matches.map(({ name, savedAt, record }) => ({
123
+ name,
124
+ savedAt,
125
+ data3mf: record?.data3mf || null,
126
+ thumbnail: record?.thumbnail || null,
127
+ }));
128
+ const emptyMessage = records.length ? 'No components match the search.' : 'No stored components found.';
129
+ gallery.setEmptyMessage(emptyMessage);
130
+ gallery.setIconsOnly(iconsOnly);
131
+ gallery.render(mapped);
132
+ };
133
+
134
+ viewToggle.addEventListener('click', () => {
135
+ iconsOnly = !iconsOnly;
136
+ updateViewToggle();
137
+ gallery.setIconsOnly(iconsOnly);
138
+ render();
139
+ });
140
+
141
+ search.addEventListener('input', render);
142
+ render();
143
+
144
+ requestAnimationFrame(() => {
145
+ try { search.focus(); } catch {}
146
+ });
147
+ });
148
+ }
149
+
150
+ (function ensureStyles() {
151
+ if (typeof document === 'undefined') return;
152
+ if (document.getElementById('component-selector-styles')) return;
153
+ const style = document.createElement('style');
154
+ style.id = 'component-selector-styles';
155
+ style.textContent = `
156
+ .component-selector-overlay {
157
+ position: fixed;
158
+ inset: 0;
159
+ background: rgba(0, 0, 0, 0.45);
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ z-index: 2000;
164
+ }
165
+ .component-selector-panel {
166
+ width: min(560px, 90vw);
167
+ max-height: 80vh;
168
+ background: #111827;
169
+ border: 1px solid #1f2937;
170
+ border-radius: 12px;
171
+ box-shadow: 0 24px 48px rgba(0,0,0,0.45);
172
+ display: flex;
173
+ flex-direction: column;
174
+ overflow: hidden;
175
+ color: #e5e7eb;
176
+ font: 14px/1.4 ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
177
+ }
178
+ .cs-header {
179
+ font-weight: 600;
180
+ padding: 14px 18px;
181
+ border-bottom: 1px solid #1f2937;
182
+ background: rgba(255,255,255,0.03);
183
+ }
184
+ .cs-controls {
185
+ display: flex;
186
+ align-items: center;
187
+ gap: 8px;
188
+ padding: 12px 18px 0 18px;
189
+ }
190
+ .cs-search {
191
+ margin: 0;
192
+ padding: 8px 10px;
193
+ border-radius: 8px;
194
+ border: 1px solid #374151;
195
+ background: #0b0e14;
196
+ color: inherit;
197
+ outline: none;
198
+ flex: 1 1 auto;
199
+ }
200
+ .cs-search:focus {
201
+ border-color: #3b82f6;
202
+ box-shadow: 0 0 0 3px rgba(59,130,246,0.25);
203
+ }
204
+ .cs-list {
205
+ flex: 1 1 auto;
206
+ overflow-y: auto;
207
+ padding: 12px;
208
+ }
209
+ .cs-view-toggle {
210
+ flex: 0 0 auto;
211
+ }
212
+ .cs-empty {
213
+ padding: 32px 18px;
214
+ text-align: center;
215
+ color: #9ca3af;
216
+ }
217
+ .cs-footer {
218
+ padding: 12px 18px;
219
+ border-top: 1px solid #1f2937;
220
+ display: flex;
221
+ justify-content: flex-end;
222
+ }
223
+ .cs-btn {
224
+ appearance: none;
225
+ border: 1px solid #374151;
226
+ background: rgba(255,255,255,0.05);
227
+ color: #f9fafb;
228
+ padding: 6px 12px;
229
+ border-radius: 8px;
230
+ font-weight: 600;
231
+ cursor: pointer;
232
+ transition: border-color 0.15s ease, background-color 0.15s ease;
233
+ }
234
+ .cs-btn:hover {
235
+ border-color: #3b82f6;
236
+ background: rgba(59,130,246,0.12);
237
+ }
238
+ `;
239
+ document.head.appendChild(style);
240
+ })();
@@ -0,0 +1,386 @@
1
+ // CombinedTransformControls - lightweight move + rotate gizmo
2
+ // Drop-in replacement for three/examples TransformControls used by this app.
3
+ // Focuses on orthographic cameras and the needs of the BREP Viewer.
4
+ //
5
+ // Public API compatibility (subset):
6
+ // - constructor(camera, domElement)
7
+ // - extends THREE.Object3D so it can be added to the scene
8
+ // - properties: enabled, dragging, mode, showX/Y/Z, isTransformGizmo
9
+ // - methods: attach(obj), detach(), setMode(mode), getMode(), update(), dispose(), getHelper()
10
+ // - events: 'change', 'dragging-changed', 'objectChange'
11
+ // - picking roots available at this.gizmo.picker.translate / .rotate
12
+ //
13
+ import * as THREE from 'three';
14
+
15
+ export class CombinedTransformControls extends THREE.Object3D {
16
+ constructor(camera, domElement) {
17
+ super();
18
+ this.type = 'CombinedTransformControls';
19
+ this.camera = camera;
20
+ this.domElement = domElement;
21
+ this.enabled = true;
22
+ this.dragging = false;
23
+ this.mode = 'translate'; // kept for compatibility; both gizmos are active
24
+ this.showX = true; this.showY = true; this.showZ = true;
25
+ this.isTransformGizmo = true; // used by PartHistory cleanup logic
26
+ this._sizeMultiplier = 2; // larger default on‑screen size
27
+ this.renderOrder = 50000; // Render on top of all other geometry
28
+
29
+ this.target = null; // Object3D we drive
30
+
31
+ this._raycaster = new THREE.Raycaster();
32
+ this._pointer = new THREE.Vector2();
33
+ this._tmpV = new THREE.Vector3();
34
+ this._tmpV2 = new THREE.Vector3();
35
+ this._tmpQ = new THREE.Quaternion();
36
+ this._plane = new THREE.Plane();
37
+
38
+ // Visuals
39
+ this.gizmo = this._buildGizmo();
40
+ this.add(this.gizmo.root);
41
+
42
+ // Events
43
+ this._onPointerDown = this._handlePointerDown.bind(this);
44
+ this._onPointerMove = this._handlePointerMove.bind(this);
45
+ this._onPointerUp = this._handlePointerUp.bind(this);
46
+ if (this.domElement) {
47
+ this.domElement.addEventListener('pointerdown', this._onPointerDown, { passive: false });
48
+ window.addEventListener('pointermove', this._onPointerMove, { passive: false });
49
+ window.addEventListener('pointerup', this._onPointerUp, { passive: false, capture: true });
50
+ }
51
+ }
52
+
53
+ dispose() {
54
+ try { this.domElement?.removeEventListener('pointerdown', this._onPointerDown); } catch {}
55
+ try { window.removeEventListener('pointermove', this._onPointerMove); } catch {}
56
+ try { window.removeEventListener('pointerup', this._onPointerUp, { capture: true }); } catch {}
57
+ }
58
+
59
+ getHelper() { return this; }
60
+ getMode() { return this.mode; }
61
+ setMode(mode) { this.mode = String(mode || 'translate'); }
62
+ setSize(s) { this._sizeMultiplier = Number(s) || 1; this.update(); }
63
+
64
+ attach(object) {
65
+ this.target = object || null;
66
+ if (this.target) {
67
+ try { this.target.updateMatrixWorld(true); } catch {}
68
+ this.position.copy(this.target.getWorldPosition(new THREE.Vector3()));
69
+ this.quaternion.copy(this.target.getWorldQuaternion(new THREE.Quaternion()));
70
+ this.updateMatrixWorld(true);
71
+ try { this.update(); } catch {}
72
+ }
73
+ }
74
+ detach() { this.target = null; }
75
+
76
+ update() {
77
+ // Keep a roughly constant on‑screen scale (ortho-friendly)
78
+ const scale = this._computeGizmoScale() * (this._sizeMultiplier || 1);
79
+ this.gizmo.root.scale.setScalar(scale);
80
+ // Face camera for labels
81
+ if (this.gizmo && this.gizmo.labels) {
82
+ const q = this.camera.quaternion;
83
+ for (const s of this.gizmo.labels) s.quaternion.copy(q);
84
+ }
85
+ }
86
+
87
+ // ----------------------------------------
88
+ // Internals: visuals
89
+ // ----------------------------------------
90
+ _buildGizmo() {
91
+ const root = new THREE.Group();
92
+ root.name = 'HybridXformGizmoRoot';
93
+ root.userData.excludeFromFit = true;
94
+ root.renderOrder = this.renderOrder; // Ensure gizmo renders on top of all other geometry
95
+
96
+ // For compatibility with the viewer's hover checks, expose picker roots.
97
+ // Point them at the root so our oriented per-handle meshes are included.
98
+ const picker = { translate: root, rotate: root };
99
+
100
+ // Materials - using overlay pattern (depthTest: false, depthWrite: false)
101
+ const mAxis = new THREE.MeshBasicMaterial({ color: 0xbfbfbf, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
102
+ const mArrow = new THREE.MeshBasicMaterial({ color: 0xf2c14e, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
103
+ const mDot = new THREE.MeshBasicMaterial({ color: 0xf29e4c, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
104
+
105
+ // Geometries (shared)
106
+ const gRod = new THREE.CylinderGeometry(0.03, 0.03, 1.0, 16);
107
+ const gArrow = new THREE.ConeGeometry(0.12, 0.4, 20);
108
+ const gDot = new THREE.SphereGeometry(0.12, 16, 12);
109
+
110
+ // Axis builders
111
+ const axes = [];
112
+ const addAxis = (axis, colorText, spriteLabel) => {
113
+ const group = new THREE.Group();
114
+ group.renderOrder = this.renderOrder; // Ensure gizmo renders on top of all other geometry
115
+ group.name = `Axis${axis}`;
116
+
117
+ const rod = new THREE.Mesh(gRod, mAxis);
118
+ rod.renderOrder = this.renderOrder;
119
+ rod.position.y = 0.5; // rod extends from center along +Y before orientation
120
+ group.add(rod);
121
+
122
+ const tip = new THREE.Mesh(gArrow, mArrow);
123
+ tip.renderOrder = this.renderOrder;
124
+ tip.position.y = 1.0 + 0.125;
125
+ tip.userData.handle = { kind: 'translate', axis };
126
+ group.add(tip);
127
+
128
+ // Orient group
129
+ if (axis === 'X') group.rotation.z = -Math.PI / 2;
130
+ if (axis === 'Z') group.rotation.x = -Math.PI / 2;
131
+
132
+ // Label sprite (XC/YC/ZC)
133
+ const spr = this._makeTextSprite(`${axis}C`, colorText);
134
+ // Place label along the axis positive direction (local +Y before rotation)
135
+ spr.position.set(0, 1.3, 0);
136
+ group.add(spr);
137
+
138
+ root.add(group);
139
+ axes.push({ group, spr });
140
+ };
141
+
142
+ addAxis('X', '#ff6666');
143
+ addAxis('Y', '#7ddc6f');
144
+ addAxis('Z', '#6aa9ff');
145
+
146
+ // Rotation arcs: quarter circles in XY (Z axis), YZ (X axis), ZX (Y axis)
147
+ const rot = {};
148
+ const addRotate = (axis) => {
149
+ const grp = new THREE.Group();
150
+ grp.name = `Rotate${axis}`;
151
+ grp.renderOrder = this.renderOrder;
152
+ const r = 0.9;
153
+ const arcShape = new THREE.BufferGeometry().setFromPoints(
154
+ Array.from({ length: 33 }, (_, i) => {
155
+ const t = (i / 32) * (Math.PI / 2);
156
+ return new THREE.Vector3(Math.cos(t) * r, Math.sin(t) * r, 0);
157
+ })
158
+ );
159
+ const arcMat = new THREE.LineBasicMaterial({ color: 0xe0e0e0, linewidth: 2, toneMapped: false, depthTest: false, depthWrite: false, transparent: true });
160
+ const arc = new THREE.Line(arcShape, arcMat);
161
+ arc.renderOrder = this.renderOrder;
162
+ grp.add(arc);
163
+
164
+ // Single decorative dot along the arc (one per axis)
165
+ const tDot = Math.PI / 4; // 45° along the arc
166
+ const dot = new THREE.Mesh(gDot, mDot);
167
+ dot.position.set(Math.cos(tDot) * r, Math.sin(tDot) * r, 0);
168
+ dot.renderOrder = this.renderOrder;
169
+ // Make the dot itself a rotate handle so dragging it feels natural
170
+ dot.userData.handle = { kind: 'rotate', axis };
171
+ grp.add(dot);
172
+
173
+ // Orient to axis
174
+ if (axis === 'X') grp.rotation.y = Math.PI / 2; // arc in YZ plane -> rotate around X
175
+ if (axis === 'Y') grp.rotation.x = -Math.PI / 2; // arc in ZX plane -> rotate around Y
176
+ // axis Z: default in XY plane
177
+
178
+ root.add(grp);
179
+ rot[axis] = { group: grp, dot, radius: r };
180
+ };
181
+
182
+ addRotate('Z');
183
+ addRotate('Y');
184
+ addRotate('X');
185
+
186
+ return { root, picker, labels: axes.map(a => a.spr), rot };
187
+ }
188
+
189
+ _makeTextSprite(text, color = '#ffffff') {
190
+ const size = 256;
191
+ const cvs = document.createElement('canvas');
192
+ cvs.width = cvs.height = size;
193
+ const ctx = cvs.getContext('2d');
194
+ ctx.clearRect(0, 0, size, size);
195
+ ctx.fillStyle = 'rgba(0,0,0,0)';
196
+ ctx.fillRect(0, 0, size, size);
197
+ ctx.font = 'bold 64px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
198
+ ctx.textAlign = 'center';
199
+ ctx.textBaseline = 'middle';
200
+ ctx.fillStyle = color;
201
+ ctx.fillText(String(text || ''), size / 2, size / 2);
202
+ const tex = new THREE.CanvasTexture(cvs);
203
+ tex.colorSpace = THREE.SRGBColorSpace;
204
+ const mat = new THREE.SpriteMaterial({ map: tex, transparent: true, depthTest: false, depthWrite: false });
205
+ const spr = new THREE.Sprite(mat);
206
+ spr.scale.setScalar(0.6);
207
+ spr.renderOrder = this.renderOrder;
208
+ return spr;
209
+ }
210
+
211
+ _computeGizmoScale() {
212
+ // For OrthographicCamera, constant screen size ≈ inverse of zoom.
213
+ const cam = this.camera;
214
+ if (cam && cam.isOrthographicCamera) {
215
+ const z = Math.max(0.0001, cam.zoom || 1);
216
+ return 1 / z;
217
+ }
218
+ // Perspective: scale with distance, using simple heuristic
219
+ const pos = this.getWorldPosition(this._tmpV);
220
+ const camPos = this.camera.getWorldPosition(this._tmpV2);
221
+ const d = pos.distanceTo(camPos);
222
+ const f = Math.tan((this.camera.fov || 50) * Math.PI / 360) * 2.0;
223
+ return (d * f) / 10; // heuristic constant
224
+ }
225
+
226
+ // ----------------------------------------
227
+ // Internals: interaction
228
+ // ----------------------------------------
229
+ _setPointerFromEvent(e) {
230
+ const rect = this.domElement.getBoundingClientRect();
231
+ const x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
232
+ const y = -((e.clientY - rect.top) / rect.height) * 2 + 1;
233
+ this._pointer.set(x, y);
234
+ }
235
+
236
+ _intersections(root) {
237
+ this._raycaster.setFromCamera(this._pointer, this.camera);
238
+
239
+ // Fix ray origin - ensure it starts from behind the camera
240
+ const ray = this._raycaster.ray;
241
+ if (this.camera.isOrthographicCamera) {
242
+ // For orthographic cameras, move the origin back along the camera's forward direction
243
+ const backwardDistance = 1000; // Large distance to ensure we're behind all objects
244
+ ray.origin.add(ray.direction.clone().multiplyScalar(-backwardDistance));
245
+ } else if (this.camera.isPerspectiveCamera) {
246
+ // For perspective cameras, use the camera position as origin
247
+ ray.origin.copy(this.camera.position);
248
+ }
249
+
250
+ return this._raycaster.intersectObject(root, true) || [];
251
+ }
252
+
253
+ _handlePointerDown(e) {
254
+ if (!this.enabled || !this.visible) return;
255
+ if (!this.target) return;
256
+ this._setPointerFromEvent(e);
257
+ // Prefer picker matching current mode, then fallback to all
258
+ const giz = this.gizmo;
259
+ const pickRoot = giz.picker[this.mode] || giz.root; // tolerate missing picker grouping
260
+ const hits = this._intersections(pickRoot);
261
+ const hit = Array.isArray(hits) ? hits.find(it => it?.object?.userData?.handle) : null;
262
+ if (!hit) return;
263
+ const h = hit.object.userData.handle;
264
+ if (!h || !h.kind) return;
265
+ e.preventDefault();
266
+ e.stopPropagation?.();
267
+
268
+ this._drag = this._drag || {};
269
+ this._drag.handle = h; // { kind, axis }
270
+ // Use the gizmo's current world pose as the drag reference so
271
+ // subsequent drags operate relative to the gizmo orientation/position
272
+ try { this.updateMatrixWorld(true); } catch {}
273
+ this._drag.startPos = this.getWorldPosition(new THREE.Vector3());
274
+ this._drag.startQuat = this.getWorldQuaternion(new THREE.Quaternion());
275
+ this._drag.axis = this._axisWorld(h.axis);
276
+
277
+ // Establish reference plane and initial point
278
+ if (h.kind === 'translate') {
279
+ const camDir = this.camera.getWorldDirection(new THREE.Vector3());
280
+ // screen plane through startPos
281
+ this._plane.setFromNormalAndCoplanarPoint(camDir, this._drag.startPos);
282
+ } else if (h.kind === 'rotate') {
283
+ this._plane.setFromNormalAndCoplanarPoint(this._drag.axis, this._drag.startPos);
284
+ }
285
+ this._drag.startPoint = this._planeIntersect();
286
+ if (!this._drag.startPoint) { this._drag = null; return; }
287
+
288
+ // For rotation, track incremental deltas so angles can exceed 180°
289
+ if (h.kind === 'rotate') {
290
+ this._drag.prevPoint = this._drag.startPoint.clone();
291
+ const rotRef = (this.gizmo && this.gizmo.rot) ? this.gizmo.rot[h.axis] : null;
292
+ this._drag.rotVis = rotRef || null;
293
+ }
294
+
295
+ this.dragging = true;
296
+ this.dispatchEvent({ type: 'dragging-changed', value: true });
297
+ }
298
+
299
+ _handlePointerMove(e) {
300
+ if (!this.dragging || !this._drag) return;
301
+ this._setPointerFromEvent(e);
302
+ const p = this._planeIntersect();
303
+ if (!p) return;
304
+
305
+ const { handle, startPos, startQuat, axis, startPoint } = this._drag;
306
+ if (handle.kind === 'translate') {
307
+ const diff = this._tmpV.copy(p).sub(startPoint);
308
+ const amt = diff.dot(axis);
309
+ const pos = this._tmpV2.copy(startPos).add(this._tmpV.copy(axis).multiplyScalar(amt));
310
+ this.target.position.copy(pos);
311
+ } else if (handle.kind === 'rotate') {
312
+ // Compute incremental angle since last move to avoid wrap-around at 180°
313
+ const prev = (this._drag.prevPoint || startPoint);
314
+ const vPrev = this._tmpV.copy(prev).sub(startPos).normalize();
315
+ const vNow = this._tmpV2.copy(p).sub(startPos).normalize();
316
+ const cross = new THREE.Vector3().crossVectors(vPrev, vNow);
317
+ const dot = THREE.MathUtils.clamp(vPrev.dot(vNow), -1, 1);
318
+ const dAngle = Math.atan2(cross.dot(axis), dot);
319
+
320
+ // Apply incremental rotation using the current axis orientation
321
+ const currentAxis = this._axisWorld(handle.axis); // Get current axis orientation
322
+ const deltaQ = new THREE.Quaternion().setFromAxisAngle(currentAxis, dAngle);
323
+ this.target.quaternion.multiplyQuaternions(deltaQ, this.target.quaternion);
324
+
325
+ this._drag.prevPoint = p.clone();
326
+
327
+ // Move the decorative dot along the circle perpendicular to the axis
328
+ try {
329
+ const rotVis = this._drag.rotVis;
330
+ if (rotVis && rotVis.dot && rotVis.group && handle.axis) {
331
+ const r = rotVis.radius || 0.9;
332
+
333
+ // Keep the dot at a fixed position during drag to avoid jumping
334
+ // The visual feedback is primarily from the object rotation itself
335
+ // We could calculate the exact angle, but a fixed position works fine for UX
336
+ const fixedAngle = Math.PI / 4; // 45° - same as initial position
337
+ rotVis.dot.position.set(Math.cos(fixedAngle) * r, Math.sin(fixedAngle) * r, 0);
338
+ }
339
+ } catch {}
340
+ }
341
+
342
+ // Keep gizmo aligned with target (position + rotation)
343
+ this.position.copy(this.target.position);
344
+ this.quaternion.copy(this.target.quaternion);
345
+ this.updateMatrixWorld(true);
346
+
347
+ this.dispatchEvent({ type: 'change' });
348
+ }
349
+
350
+ _handlePointerUp(e) {
351
+ if (!this.dragging) return;
352
+ this.dragging = false;
353
+ this._drag = null;
354
+ this.dispatchEvent({ type: 'dragging-changed', value: false });
355
+ this.dispatchEvent({ type: 'objectChange' });
356
+ }
357
+
358
+ _axisWorld(axis) {
359
+ const v = new THREE.Vector3(
360
+ axis === 'X' ? 1 : 0,
361
+ axis === 'Y' ? 1 : 0,
362
+ axis === 'Z' ? 1 : 0,
363
+ );
364
+ // Axis is defined in gizmo/target local; rotate to world using current gizmo quaternion
365
+ return v.applyQuaternion(this.quaternion).normalize();
366
+ }
367
+
368
+ _planeIntersect() {
369
+ this._raycaster.setFromCamera(this._pointer, this.camera);
370
+
371
+ // Fix ray origin - ensure it starts from behind the camera
372
+ const ray = this._raycaster.ray;
373
+ if (this.camera.isOrthographicCamera) {
374
+ // For orthographic cameras, move the origin back along the camera's forward direction
375
+ const backwardDistance = 1000; // Large distance to ensure we're behind all objects
376
+ ray.origin.add(ray.direction.clone().multiplyScalar(-backwardDistance));
377
+ } else if (this.camera.isPerspectiveCamera) {
378
+ // For perspective cameras, use the camera position as origin
379
+ ray.origin.copy(this.camera.position);
380
+ }
381
+
382
+ const p = new THREE.Vector3();
383
+ const hit = this._raycaster.ray.intersectPlane(this._plane, p);
384
+ return hit ? p.clone() : null;
385
+ }
386
+ }