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,236 @@
1
+ import * as THREE from 'three';
2
+
3
+ const COLOR_DEFAULT = 0x69a8ff;
4
+ const COLOR_HOVER = 0xffd54a;
5
+ const COLOR_SELECTED = 0x6fe26f;
6
+
7
+ // Grouped glyph renderer: draws small glyphs for non-dimension constraints,
8
+ // grouping those that act on the same set of points at a single location.
9
+ // Also records per-constraint centers for hit-testing.
10
+ export function drawConstraintGlyphs(inst, constraints) {
11
+ if (!inst || !inst._dim3D || !inst._lock || !inst._solver) return;
12
+ const s = inst._solver.sketchObject;
13
+ inst._glyphCenters = new Map();
14
+ const to3 = (u, v) => new THREE.Vector3()
15
+ .copy(inst._lock.basis.origin)
16
+ .addScaledVector(inst._lock.basis.x, u)
17
+ .addScaledVector(inst._lock.basis.y, v);
18
+ // Project plane (u,v) to screen and place an HTML glyph label with the unicode char
19
+ const placeGlyphLabel = (c, text, u, v, colorHex) => {
20
+ try {
21
+ if (!inst._dimRoot) return;
22
+ const world = to3(u, v);
23
+ const pt = world.project(inst.viewer.camera);
24
+ const rect = inst.viewer.renderer.domElement.getBoundingClientRect();
25
+ const x = (pt.x * 0.5 + 0.5) * rect.width;
26
+ const y = (-pt.y * 0.5 + 0.5) * rect.height;
27
+ const el = document.createElement('div');
28
+ el.className = 'glyph-label';
29
+ el.textContent = String(text);
30
+ el.style.position = 'absolute';
31
+ el.style.left = `${Math.round(x)}px`;
32
+ el.style.top = `${Math.round(y)}px`;
33
+ el.style.transform = 'translate(-50%, -50%)';
34
+ el.style.pointerEvents = 'auto';
35
+ // Prevent selecting glyph text while dragging/hovering
36
+ el.style.userSelect = 'none';
37
+ el.style.webkitUserSelect = 'none';
38
+ el.style.MozUserSelect = 'none';
39
+ el.setAttribute('draggable', 'false');
40
+ el.onselectstart = () => false;
41
+ el.style.font = '14px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace';
42
+ el.style.lineHeight = '1';
43
+ el.style.color = '#e6e6e6';
44
+ el.style.padding = '1px 6px';
45
+ el.style.borderRadius = '6px';
46
+ el.style.border = '1px solid #364053';
47
+ el.style.background = 'rgba(20,24,30,.85)';
48
+ if (colorHex === COLOR_SELECTED) {
49
+ el.style.background = 'rgba(111,226,111,.16)';
50
+ el.style.border = '1px solid #2f6d2f';
51
+ } else if (colorHex === COLOR_HOVER) {
52
+ el.style.background = 'rgba(255,213,74,.12)';
53
+ el.style.border = '1px solid #6f5a12';
54
+ }
55
+
56
+ // Interactions: click to toggle selection; hover to reflect
57
+ el.addEventListener('pointerdown', (e) => {
58
+ try { if (inst.viewer?.controls) inst.viewer.controls.enabled = false; } catch { }
59
+ try { el.setPointerCapture(e.pointerId); } catch { }
60
+ e.preventDefault(); e.stopPropagation();
61
+ });
62
+ el.addEventListener('pointerup', (e) => {
63
+ try { el.releasePointerCapture(e.pointerId); } catch { }
64
+ try { if (inst.viewer?.controls) inst.viewer.controls.enabled = true; } catch { }
65
+ try { inst.toggleSelectConstraint?.(c.id); } catch { }
66
+ e.preventDefault(); e.stopPropagation();
67
+ });
68
+ el.addEventListener('pointerenter', () => { try { inst.hoverConstraintFromLabel?.(c.id); } catch { } });
69
+ el.addEventListener('pointerleave', () => { try { inst.clearHoverFromLabel?.(c.id); } catch { } });
70
+
71
+ inst._dimRoot.appendChild(el);
72
+ } catch { }
73
+ };
74
+ const rect = inst.viewer.renderer.domElement.getBoundingClientRect();
75
+ const wpp = worldPerPixel(inst.viewer.camera, rect.width, rect.height);
76
+ const base = Math.max(0.1, wpp * 14);
77
+ const handleR = Math.max(0.02, wpp * 8 * 0.5);
78
+ const iconR = Math.max(base * 0.9, handleR * 1.9); // approx glyph half size in world units
79
+ const P = (id) => s.points.find((p) => p.id === id);
80
+
81
+ // Nudge a (u,v) position away from nearby sketch points to avoid overlap
82
+ const nudgeFromPoints = (u, v) => {
83
+ const pts = (s && Array.isArray(s.points)) ? s.points : [];
84
+ // Ensure clearance relative to both handle size and glyph label size
85
+ const minDist = iconR;
86
+ let uu = u, vv = v;
87
+ let iter = 0;
88
+ while (iter++ < 10) {
89
+ let nearest = null, nd = Infinity;
90
+ for (const p of pts) {
91
+ const d = Math.hypot(uu - p.x, vv - p.y);
92
+ if (d < nd) { nd = d; nearest = p; }
93
+ }
94
+ if (!nearest || nd >= minDist) break;
95
+ let dx = uu - nearest.x, dy = vv - nearest.y;
96
+ let L = Math.hypot(dx, dy);
97
+ if (L < 1e-6) { dx = 0.70710678; dy = 0.70710678; L = 1; }
98
+ const push = (minDist - nd) + (0.35 * minDist);
99
+ uu = nearest.x + (dx / L) * (nd + push);
100
+ vv = nearest.y + (dy / L) * (nd + push);
101
+ }
102
+ return { u: uu, v: vv };
103
+ };
104
+
105
+ // Avoid both points and existing glyphs; keeps result near (u0,v0)
106
+ const placedIcons = [];
107
+ const freeFromIcons = (u, v) => placedIcons.every(p => Math.hypot(u - p.u, v - p.v) >= iconR * 1.2);
108
+ const avoidAll = (u0, v0) => {
109
+ // First clear from points
110
+ let p = nudgeFromPoints(u0, v0);
111
+ if (freeFromIcons(p.u, p.v)) return p;
112
+ // Spiral search outwards
113
+ const step = iconR * 0.6;
114
+ const maxRings = 16;
115
+ for (let ring = 1; ring <= maxRings; ring++) {
116
+ const r = ring * step;
117
+ const spokes = 8 + ring * 2;
118
+ for (let i = 0; i < spokes; i++) {
119
+ const ang = (i / spokes) * Math.PI * 2;
120
+ const cu = u0 + Math.cos(ang) * r;
121
+ const cv = v0 + Math.sin(ang) * r;
122
+ const q = nudgeFromPoints(cu, cv);
123
+ if (freeFromIcons(q.u, q.v)) return q;
124
+ }
125
+ }
126
+ return p; // fallback
127
+ };
128
+ // Build groups by sorted unique point set
129
+ const groups = new Map();
130
+ for (const c of (constraints || [])) {
131
+ if (!c || c.type === '⟺' || c.type === '∠') continue;
132
+ const ids = Array.from(new Set((c.points || []).map(Number))).sort((a, b) => a - b);
133
+ if (!ids.length) continue;
134
+ const key = ids.join(',');
135
+ const arr = groups.get(key) || []; arr.push(c); groups.set(key, arr);
136
+ }
137
+
138
+ // Compute anchor per group
139
+ const lineIntersect = (A, B, C, D) => {
140
+ // Returns intersection of infinite lines AB and CD; fallback to average of midpoints
141
+ const A1 = B.y - A.y; const B1 = A.x - B.x; const C1 = A1 * A.x + B1 * A.y;
142
+ const A2 = D.y - C.y; const B2 = C.x - D.x; const C2 = A2 * C.x + B2 * C.y;
143
+ const den = A1 * B2 - A2 * B1;
144
+ if (Math.abs(den) < 1e-9) {
145
+ const m1 = { x: (A.x + B.x) * 0.5, y: (A.y + B.y) * 0.5 };
146
+ const m2 = { x: (C.x + D.x) * 0.5, y: (C.y + D.y) * 0.5 };
147
+ return { x: (m1.x + m2.x) * 0.5, y: (m1.y + m2.y) * 0.5 };
148
+ }
149
+ const x = (B2 * C1 - B1 * C2) / den;
150
+ const y = (A1 * C2 - A2 * C1) / den;
151
+ return { x, y };
152
+ };
153
+ const anchorFor = (ids, arr) => {
154
+ // If any perpendicular exists in this group, use line intersection of its two lines as anchor
155
+ const perp = (arr || []).find(c => c && c.type === '⟂' && Array.isArray(c.points) && c.points.length >= 4);
156
+ if (perp) {
157
+ const A = P(perp.points[0]); const B = P(perp.points[1]);
158
+ const C = P(perp.points[2]); const D = P(perp.points[3]);
159
+ if (A && B && C && D) {
160
+ const I = lineIntersect(A, B, C, D);
161
+ const off = nudgeFromPoints(I.x, I.y);
162
+ return { u: off.u, v: off.v };
163
+ }
164
+ }
165
+ // If the group is only coincident constraints, anchor near the first point
166
+ if ((arr || []).length && (arr || []).every(c => c && c.type === '≡')) {
167
+ const p0 = P(ids[0]);
168
+ if (p0) {
169
+ const off = nudgeFromPoints(p0.x, p0.y);
170
+ return { u: off.u, v: off.v };
171
+ }
172
+ }
173
+ // Default: centroid of unique points, nudged slightly for visibility
174
+ let sx = 0, sy = 0, n = 0;
175
+ for (const id of ids) { const p = P(id); if (p) { sx += p.x; sy += p.y; n++; } }
176
+ if (!n) return { u: 0, v: 0 };
177
+ const u = sx / n, v = sy / n;
178
+ const off = nudgeFromPoints(u, v);
179
+ return { u: off.u, v: off.v };
180
+ };
181
+
182
+ // Draw each group: lay out symbols in a row centered at anchor
183
+ const selSet = new Set(Array.from(inst._selection || []).filter(it => it.type === 'constraint').map(it => it.id));
184
+ const hovId = (inst._hover && inst._hover.type === 'constraint') ? inst._hover.id : null;
185
+
186
+ for (const [key, arr] of groups.entries()) {
187
+ const ids = key.split(',').map(Number);
188
+ const anchor = anchorFor(ids, arr);
189
+ const spacing = base * 0.7;
190
+ const startU = anchor.u - spacing * (arr.length - 1) / 2;
191
+ const y = anchor.v;
192
+ for (let i = 0; i < arr.length; i++) {
193
+ const c = arr[i];
194
+ const cx = startU + i * spacing;
195
+ const adj = avoidAll(cx, y);
196
+ // Record for hit-testing
197
+ try { inst._glyphCenters.set(c.id, { u: adj.u, v: adj.v }); } catch { }
198
+ placedIcons.push({ u: adj.u, v: adj.v });
199
+ // Small pick radius disk (invisible) for selection
200
+ try {
201
+ const pickR = iconR;
202
+ const g = new THREE.CircleGeometry(pickR, 20);
203
+ // Orient the circle in plane XY mapped to sketch plane
204
+ const X = inst._lock.basis.x.clone().normalize();
205
+ const Y = inst._lock.basis.y.clone().normalize();
206
+ const Z = new THREE.Vector3().crossVectors(X, Y).normalize();
207
+ const m = new THREE.Matrix4().makeBasis(X, Y, Z).setPosition(to3(adj.u, adj.v));
208
+ const mat = new THREE.MeshBasicMaterial({ visible: false });
209
+ const mesh = new THREE.Mesh(g, mat);
210
+ mesh.applyMatrix4(m);
211
+ mesh.renderOrder = 10030;
212
+
213
+ mesh.userData = { kind: 'glyphHit', cid: c.id };
214
+ inst._dim3D.add(mesh);
215
+ } catch { }
216
+
217
+ // Draw the glyph symbol itself as unicode character from constraint.type
218
+ try {
219
+ const color = selSet.has(c.id) ? COLOR_SELECTED : (hovId === c.id ? COLOR_HOVER : COLOR_DEFAULT);
220
+ placeGlyphLabel(c, c.type || '?', adj.u, adj.v, color);
221
+ } catch { }
222
+ }
223
+ }
224
+ }
225
+
226
+ function worldPerPixel(camera, width, height) {
227
+ if (camera && camera.isOrthographicCamera) {
228
+ const zoom = typeof camera.zoom === 'number' && camera.zoom > 0 ? camera.zoom : 1;
229
+ const wppX = (camera.right - camera.left) / (width * zoom);
230
+ const wppY = (camera.top - camera.bottom) / (height * zoom);
231
+ return Math.max(wppX, wppY);
232
+ }
233
+ const dist = camera.position.length();
234
+ const fovRad = (camera.fov * Math.PI) / 180;
235
+ return (2 * Math.tan(fovRad / 2) * dist) / height;
236
+ }
@@ -0,0 +1,60 @@
1
+ // Sidebar + 3D hover/selection coloring helpers
2
+
3
+ export function updateListHighlights(inst) {
4
+ if (!inst || !inst._acc) return;
5
+ const mapKey = (t, id) => (t === 'point' ? `p:${id}` : t === 'geometry' ? `g:${id}` : t === 'constraint' ? `c:${id}` : null);
6
+ const sel = new Set(Array.from(inst._selection || []).map((i) => mapKey(i.type, i.id)).filter(Boolean));
7
+ const hov = inst._hover ? mapKey(inst._hover.type, inst._hover.id) : null;
8
+
9
+ const rows = Array.from(inst._acc.uiElement.querySelectorAll('.sk-row'));
10
+ for (const r of rows) {
11
+ const btn = r.querySelector('[data-act]');
12
+ const key = btn ? btn.getAttribute('data-act') : null;
13
+ const selected = key && sel.has(key);
14
+ const hovered = key && hov === key;
15
+
16
+ // Row tint
17
+ r.style.background = selected
18
+ ? 'rgba(111,226,111,.12)'
19
+ : hovered
20
+ ? 'rgba(255,213,74,.10)'
21
+ : 'transparent';
22
+ r.style.borderRadius = '6px';
23
+
24
+ // Button visual state
25
+ if (btn) {
26
+ if (selected) {
27
+ btn.style.background = 'rgba(111,226,111,.10)';
28
+ btn.style.border = '1px solid #2f6d2f';
29
+ btn.style.color = '#d7ffd7';
30
+ } else if (hovered) {
31
+ btn.style.background = 'rgba(255,213,74,.08)';
32
+ btn.style.border = '1px solid #6f5a12';
33
+ btn.style.color = '#ffe599';
34
+ } else {
35
+ // Reset to defaults used when rendering the list
36
+ btn.style.background = 'transparent';
37
+ btn.style.border = '1px solid #364053';
38
+ btn.style.color = '#ddd';
39
+ }
40
+ }
41
+ }
42
+ }
43
+
44
+ export function applyHoverAndSelectionColors(inst) {
45
+ if (!inst || !inst._sketchGroup) return;
46
+ const hov = inst._hover;
47
+ const isSel = (kind, id) => Array.from(inst._selection).some(s => s.type === (kind === 'point' ? 'point' : 'geometry') && s.id === id);
48
+ const isHov = (kind, id) => hov && ((hov.type === 'point' && kind === 'point' && hov.id === id) || (hov.type === 'geometry' && kind === 'geometry' && hov.id === id));
49
+ for (const ch of inst._sketchGroup.children) {
50
+ const ud = ch.userData || {};
51
+ if (ud.kind === 'point') {
52
+ const base = ud.underConstrained ? 0xffb347 : 0x9ec9ff;
53
+ const col = isSel('point', ud.id) ? 0x6fe26f : (isHov('point', ud.id) ? 0xffd54a : base);
54
+ try { ch.material.color.setHex(col); } catch {}
55
+ } else if (ud.kind === 'geometry') {
56
+ const col = isSel('geometry', ud.id) ? 0x6fe26f : (isHov('geometry', ud.id) ? 0xffd54a : 0xffff88);
57
+ try { ch.material.color.setHex(col); } catch {}
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,5 @@
1
+ export function createAboutButton() {
2
+ const onClick = () => { try { window.open('../../help/index.html', '_blank'); } catch {} };
3
+ return { label: 'ℹ️', title: 'Open About page', onClick };
4
+ }
5
+