brep-io-kernel 1.0.0

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 (403) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +144 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/dist-kernel/help/CONTRIBUTING.html +248 -0
  5. package/dist-kernel/help/LICENSE.html +248 -0
  6. package/dist-kernel/help/MODELING.png +0 -0
  7. package/dist-kernel/help/PMI.png +0 -0
  8. package/dist-kernel/help/SKETCH.png +0 -0
  9. package/dist-kernel/help/assembly-constraints__Coincident_Constraint_dialog.png +0 -0
  10. package/dist-kernel/help/assembly-constraints___Angle_Constraint_dialog.png +0 -0
  11. package/dist-kernel/help/assembly-constraints___Distance_Constraint_dialog.png +0 -0
  12. package/dist-kernel/help/assembly-constraints___Fixed_Constraint_dialog.png +0 -0
  13. package/dist-kernel/help/assembly-constraints___Parallel_Constraint_dialog.png +0 -0
  14. package/dist-kernel/help/assembly-constraints___Touch_Align_Constraint_dialog.png +0 -0
  15. package/dist-kernel/help/assembly-constraints__angle-constraint.html +248 -0
  16. package/dist-kernel/help/assembly-constraints__coincident-constraint.html +248 -0
  17. package/dist-kernel/help/assembly-constraints__distance-constraint.html +248 -0
  18. package/dist-kernel/help/assembly-constraints__fixed-constraint.html +248 -0
  19. package/dist-kernel/help/assembly-constraints__parallel-constraint.html +248 -0
  20. package/dist-kernel/help/assembly-constraints__solver.html +248 -0
  21. package/dist-kernel/help/assembly-constraints__touch-align-constraint.html +248 -0
  22. package/dist-kernel/help/brep-api.html +263 -0
  23. package/dist-kernel/help/brep-kernel.html +258 -0
  24. package/dist-kernel/help/brep-model.html +248 -0
  25. package/dist-kernel/help/cylindrical-face-radius-embedding.html +290 -0
  26. package/dist-kernel/help/dialog-screenshots.html +248 -0
  27. package/dist-kernel/help/extruded-sketch-radius-embedding.html +336 -0
  28. package/dist-kernel/help/features__Assembly_Component_dialog.png +0 -0
  29. package/dist-kernel/help/features__Boolean_dialog.png +0 -0
  30. package/dist-kernel/help/features__Chamfer_dialog.png +0 -0
  31. package/dist-kernel/help/features__Datium_dialog.png +0 -0
  32. package/dist-kernel/help/features__Extrude_dialog.png +0 -0
  33. package/dist-kernel/help/features__Fillet_dialog.png +0 -0
  34. package/dist-kernel/help/features__Helix_dialog.png +0 -0
  35. package/dist-kernel/help/features__Hole_dialog.png +0 -0
  36. package/dist-kernel/help/features__Image_Heightmap_Solid_dialog.png +0 -0
  37. package/dist-kernel/help/features__Image_to_Face_dialog.png +0 -0
  38. package/dist-kernel/help/features__Import_3D_Model_dialog.png +0 -0
  39. package/dist-kernel/help/features__Loft_dialog.png +0 -0
  40. package/dist-kernel/help/features__Mirror_dialog.png +0 -0
  41. package/dist-kernel/help/features__Offset_Shell_dialog.png +0 -0
  42. package/dist-kernel/help/features__Overlap_Cleanup_dialog.png +0 -0
  43. package/dist-kernel/help/features__Pattern_Linear_dialog.png +0 -0
  44. package/dist-kernel/help/features__Pattern_Radial_dialog.png +0 -0
  45. package/dist-kernel/help/features__Pattern_dialog.png +0 -0
  46. package/dist-kernel/help/features__Plane_dialog.png +0 -0
  47. package/dist-kernel/help/features__Primitive_Cone_dialog.png +0 -0
  48. package/dist-kernel/help/features__Primitive_Cube_dialog.png +0 -0
  49. package/dist-kernel/help/features__Primitive_Cylinder_dialog.png +0 -0
  50. package/dist-kernel/help/features__Primitive_Pyramid_dialog.png +0 -0
  51. package/dist-kernel/help/features__Primitive_Sphere_dialog.png +0 -0
  52. package/dist-kernel/help/features__Primitive_Torus_dialog.png +0 -0
  53. package/dist-kernel/help/features__Remesh_dialog.png +0 -0
  54. package/dist-kernel/help/features__Revolve_dialog.png +0 -0
  55. package/dist-kernel/help/features__Sheet_Metal_Contour_Flange_dialog.png +0 -0
  56. package/dist-kernel/help/features__Sheet_Metal_Cutout_dialog.png +0 -0
  57. package/dist-kernel/help/features__Sheet_Metal_Flange_dialog.png +0 -0
  58. package/dist-kernel/help/features__Sheet_Metal_Tab_dialog.png +0 -0
  59. package/dist-kernel/help/features__Sketch_dialog.png +0 -0
  60. package/dist-kernel/help/features__Spline_dialog.png +0 -0
  61. package/dist-kernel/help/features__Sweep_dialog.png +0 -0
  62. package/dist-kernel/help/features__Transform_dialog.png +0 -0
  63. package/dist-kernel/help/features__Tube_dialog.png +0 -0
  64. package/dist-kernel/help/features__assembly-component.html +248 -0
  65. package/dist-kernel/help/features__boolean.html +248 -0
  66. package/dist-kernel/help/features__chamfer.html +248 -0
  67. package/dist-kernel/help/features__datium.html +248 -0
  68. package/dist-kernel/help/features__datum.html +248 -0
  69. package/dist-kernel/help/features__extrude.html +248 -0
  70. package/dist-kernel/help/features__fillet.html +248 -0
  71. package/dist-kernel/help/features__helix.html +248 -0
  72. package/dist-kernel/help/features__hole.html +248 -0
  73. package/dist-kernel/help/features__image-heightmap-solid.html +248 -0
  74. package/dist-kernel/help/features__image-to-face-2D_dialog.png +0 -0
  75. package/dist-kernel/help/features__image-to-face-3D_dialog.png +0 -0
  76. package/dist-kernel/help/features__image-to-face.html +248 -0
  77. package/dist-kernel/help/features__import-3d-model.html +248 -0
  78. package/dist-kernel/help/features__index.html +248 -0
  79. package/dist-kernel/help/features__loft.html +248 -0
  80. package/dist-kernel/help/features__mirror.html +248 -0
  81. package/dist-kernel/help/features__offset-shell.html +248 -0
  82. package/dist-kernel/help/features__pattern-linear.html +248 -0
  83. package/dist-kernel/help/features__pattern-radial.html +248 -0
  84. package/dist-kernel/help/features__pattern.html +248 -0
  85. package/dist-kernel/help/features__plane.html +248 -0
  86. package/dist-kernel/help/features__primitive-cone.html +248 -0
  87. package/dist-kernel/help/features__primitive-cube.html +248 -0
  88. package/dist-kernel/help/features__primitive-cylinder.html +248 -0
  89. package/dist-kernel/help/features__primitive-pyramid.html +248 -0
  90. package/dist-kernel/help/features__primitive-sphere.html +248 -0
  91. package/dist-kernel/help/features__primitive-torus.html +248 -0
  92. package/dist-kernel/help/features__remesh.html +248 -0
  93. package/dist-kernel/help/features__revolve.html +248 -0
  94. package/dist-kernel/help/features__sheet-metal-contour-flange.html +248 -0
  95. package/dist-kernel/help/features__sheet-metal-flange.html +248 -0
  96. package/dist-kernel/help/features__sheet-metal-tab.html +248 -0
  97. package/dist-kernel/help/features__sketch.html +248 -0
  98. package/dist-kernel/help/features__spline.html +248 -0
  99. package/dist-kernel/help/features__sweep.html +248 -0
  100. package/dist-kernel/help/features__transform.html +248 -0
  101. package/dist-kernel/help/features__tube.html +248 -0
  102. package/dist-kernel/help/file-formats.html +248 -0
  103. package/dist-kernel/help/getting-started.html +248 -0
  104. package/dist-kernel/help/highlights.html +248 -0
  105. package/dist-kernel/help/history-systems.html +248 -0
  106. package/dist-kernel/help/how-it-works.html +248 -0
  107. package/dist-kernel/help/index.html +862 -0
  108. package/dist-kernel/help/input-params-schema.html +363 -0
  109. package/dist-kernel/help/inspector-improvements.html +248 -0
  110. package/dist-kernel/help/inspector.html +248 -0
  111. package/dist-kernel/help/modes__modeling.html +248 -0
  112. package/dist-kernel/help/modes__pmi.html +248 -0
  113. package/dist-kernel/help/modes__sketch.html +248 -0
  114. package/dist-kernel/help/plugins.html +248 -0
  115. package/dist-kernel/help/pmi-annotations__Angle_Dimension_dialog.png +0 -0
  116. package/dist-kernel/help/pmi-annotations__Explode_Body_dialog.png +0 -0
  117. package/dist-kernel/help/pmi-annotations__Hole_Callout_dialog.png +0 -0
  118. package/dist-kernel/help/pmi-annotations__Leader_dialog.png +0 -0
  119. package/dist-kernel/help/pmi-annotations__Linear_Dimension_dialog.png +0 -0
  120. package/dist-kernel/help/pmi-annotations__Note_dialog.png +0 -0
  121. package/dist-kernel/help/pmi-annotations__Radial_Dimension_dialog.png +0 -0
  122. package/dist-kernel/help/pmi-annotations__angle-dimension.html +248 -0
  123. package/dist-kernel/help/pmi-annotations__explode-body.html +248 -0
  124. package/dist-kernel/help/pmi-annotations__hole-callout.html +248 -0
  125. package/dist-kernel/help/pmi-annotations__index.html +248 -0
  126. package/dist-kernel/help/pmi-annotations__leader.html +248 -0
  127. package/dist-kernel/help/pmi-annotations__linear-dimension.html +248 -0
  128. package/dist-kernel/help/pmi-annotations__note.html +248 -0
  129. package/dist-kernel/help/pmi-annotations__radial-dimension.html +248 -0
  130. package/dist-kernel/help/search-index.json +464 -0
  131. package/dist-kernel/help/simplified-radial-dimensions.html +298 -0
  132. package/dist-kernel/help/solid-methods.html +359 -0
  133. package/dist-kernel/help/table-of-contents.html +330 -0
  134. package/dist-kernel/help/ui-overview.html +248 -0
  135. package/dist-kernel/help/whats-new.html +248 -0
  136. package/package.json +54 -0
  137. package/src/BREP/AssemblyComponent.js +42 -0
  138. package/src/BREP/BREP.js +43 -0
  139. package/src/BREP/BetterSolid.js +805 -0
  140. package/src/BREP/Edge.js +103 -0
  141. package/src/BREP/Extrude.js +403 -0
  142. package/src/BREP/Face.js +187 -0
  143. package/src/BREP/MeshRepairer.js +634 -0
  144. package/src/BREP/OffsetShellSolid.js +614 -0
  145. package/src/BREP/PointCloudWrap.js +302 -0
  146. package/src/BREP/Revolve.js +345 -0
  147. package/src/BREP/SolidMethods/authoring.js +112 -0
  148. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  149. package/src/BREP/SolidMethods/chamfer.js +122 -0
  150. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  151. package/src/BREP/SolidMethods/fillet.js +792 -0
  152. package/src/BREP/SolidMethods/index.js +72 -0
  153. package/src/BREP/SolidMethods/io.js +105 -0
  154. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  155. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  156. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  157. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  158. package/src/BREP/SolidMethods/metadata.js +106 -0
  159. package/src/BREP/SolidMethods/metrics.js +51 -0
  160. package/src/BREP/SolidMethods/transforms.js +361 -0
  161. package/src/BREP/SolidMethods/visualize.js +508 -0
  162. package/src/BREP/SolidShared.js +26 -0
  163. package/src/BREP/Sweep.js +1596 -0
  164. package/src/BREP/Tube.js +857 -0
  165. package/src/BREP/Vertex.js +43 -0
  166. package/src/BREP/applyBooleanOperation.js +704 -0
  167. package/src/BREP/boundsUtils.js +48 -0
  168. package/src/BREP/chamfer.js +551 -0
  169. package/src/BREP/edgePolylineUtils.js +85 -0
  170. package/src/BREP/fillets/common.js +388 -0
  171. package/src/BREP/fillets/fillet.js +1422 -0
  172. package/src/BREP/fillets/filletGeometry.js +15 -0
  173. package/src/BREP/fillets/inset.js +389 -0
  174. package/src/BREP/fillets/offsetHelper.js +143 -0
  175. package/src/BREP/fillets/outset.js +88 -0
  176. package/src/BREP/helix.js +193 -0
  177. package/src/BREP/meshToBrep.js +234 -0
  178. package/src/BREP/primitives.js +279 -0
  179. package/src/BREP/setupManifold.js +71 -0
  180. package/src/BREP/threadGeometry.js +1120 -0
  181. package/src/BREP/triangleUtils.js +8 -0
  182. package/src/BREP/triangulate.js +608 -0
  183. package/src/FeatureRegistry.js +183 -0
  184. package/src/PartHistory.js +1132 -0
  185. package/src/UI/AccordionWidget.js +292 -0
  186. package/src/UI/CADmaterials.js +850 -0
  187. package/src/UI/EnvMonacoEditor.js +522 -0
  188. package/src/UI/FloatingWindow.js +396 -0
  189. package/src/UI/HistoryWidget.js +457 -0
  190. package/src/UI/MainToolbar.js +131 -0
  191. package/src/UI/ModelLibraryView.js +194 -0
  192. package/src/UI/OrthoCameraIdle.js +206 -0
  193. package/src/UI/PluginsWidget.js +280 -0
  194. package/src/UI/SceneListing.js +606 -0
  195. package/src/UI/SelectionFilter.js +629 -0
  196. package/src/UI/ViewCube.js +389 -0
  197. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  198. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  199. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  200. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  201. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  202. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  203. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  204. package/src/UI/assembly/constraintPointUtils.js +64 -0
  205. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  206. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  207. package/src/UI/componentSelectorModal.js +240 -0
  208. package/src/UI/controls/CombinedTransformControls.js +386 -0
  209. package/src/UI/dialogs.js +351 -0
  210. package/src/UI/expressionsManager.js +100 -0
  211. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  212. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  213. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  214. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  215. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  216. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  217. package/src/UI/featureDialogWidgets/index.js +34 -0
  218. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  219. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  220. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  221. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  222. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  223. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  224. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  225. package/src/UI/featureDialogWidgets/utils.js +43 -0
  226. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  227. package/src/UI/featureDialogs.js +1414 -0
  228. package/src/UI/fileManagerWidget.js +615 -0
  229. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  230. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  231. package/src/UI/history/historyDisplayInfo.js +133 -0
  232. package/src/UI/mobile.js +28 -0
  233. package/src/UI/objectDump.js +442 -0
  234. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  235. package/src/UI/pmi/AnnotationHistory.js +353 -0
  236. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  237. package/src/UI/pmi/BaseAnnotation.js +269 -0
  238. package/src/UI/pmi/LabelOverlay.css +102 -0
  239. package/src/UI/pmi/LabelOverlay.js +191 -0
  240. package/src/UI/pmi/PMIMode.js +1550 -0
  241. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  242. package/src/UI/pmi/annUtils.js +729 -0
  243. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  244. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  245. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  246. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  247. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  248. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  249. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  250. package/src/UI/pmi/pmiStyle.js +44 -0
  251. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  252. package/src/UI/sketcher/dimensions.js +674 -0
  253. package/src/UI/sketcher/glyphs.js +236 -0
  254. package/src/UI/sketcher/highlights.js +60 -0
  255. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  256. package/src/UI/toolbarButtons/exportButton.js +609 -0
  257. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  258. package/src/UI/toolbarButtons/importButton.js +160 -0
  259. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  260. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  261. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  262. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  263. package/src/UI/toolbarButtons/saveButton.js +99 -0
  264. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  265. package/src/UI/toolbarButtons/testsButton.js +26 -0
  266. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  267. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  268. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  269. package/src/UI/triangleDebuggerWindow.js +945 -0
  270. package/src/UI/viewer.js +4228 -0
  271. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  272. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  273. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  274. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  275. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  276. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  277. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  278. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  279. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  280. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  281. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  282. package/src/core/entities/HistoryCollectionBase.js +72 -0
  283. package/src/core/entities/ListEntityBase.js +109 -0
  284. package/src/core/entities/schemaProcesser.js +121 -0
  285. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  286. package/src/exporters/sheetMetalUnfold.js +862 -0
  287. package/src/exporters/step.js +1135 -0
  288. package/src/exporters/threeMF.js +575 -0
  289. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  290. package/src/features/boolean/BooleanFeature.js +94 -0
  291. package/src/features/chamfer/ChamferFeature.js +116 -0
  292. package/src/features/datium/DatiumFeature.js +80 -0
  293. package/src/features/edgeFeatureUtils.js +41 -0
  294. package/src/features/extrude/ExtrudeFeature.js +143 -0
  295. package/src/features/fillet/FilletFeature.js +197 -0
  296. package/src/features/helix/HelixFeature.js +405 -0
  297. package/src/features/hole/HoleFeature.js +1050 -0
  298. package/src/features/hole/screwClearance.js +86 -0
  299. package/src/features/hole/threadDesignationCatalog.js +149 -0
  300. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  301. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  302. package/src/features/imageToFace/imageEditor.js +1270 -0
  303. package/src/features/imageToFace/traceUtils.js +971 -0
  304. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  305. package/src/features/loft/LoftFeature.js +605 -0
  306. package/src/features/mirror/MirrorFeature.js +151 -0
  307. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  308. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  309. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  310. package/src/features/pattern/PatternFeature.js +275 -0
  311. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  312. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  313. package/src/features/plane/PlaneFeature.js +154 -0
  314. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  315. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  316. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  317. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  318. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  319. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  320. package/src/features/remesh/RemeshFeature.js +97 -0
  321. package/src/features/revolve/RevolveFeature.js +111 -0
  322. package/src/features/selectionUtils.js +118 -0
  323. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  324. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  325. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  326. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  327. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  328. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  329. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  330. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  331. package/src/features/sheetMetal/profileUtils.js +25 -0
  332. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  333. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  334. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  335. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  336. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  337. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  338. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  339. package/src/features/sketch/SketchFeature.js +955 -0
  340. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  341. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  342. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  343. package/src/features/spline/SplineEditorSession.js +988 -0
  344. package/src/features/spline/SplineFeature.js +1388 -0
  345. package/src/features/spline/splineUtils.js +218 -0
  346. package/src/features/sweep/SweepFeature.js +110 -0
  347. package/src/features/transform/TransformFeature.js +152 -0
  348. package/src/features/tube/TubeFeature.js +635 -0
  349. package/src/fs.proxy.js +625 -0
  350. package/src/idbStorage.js +254 -0
  351. package/src/index.js +12 -0
  352. package/src/main.js +15 -0
  353. package/src/metadataManager.js +64 -0
  354. package/src/path.proxy.js +277 -0
  355. package/src/plugins/ghLoader.worker.js +151 -0
  356. package/src/plugins/pluginManager.js +286 -0
  357. package/src/pmi/PMIViewsManager.js +134 -0
  358. package/src/services/componentLibrary.js +198 -0
  359. package/src/tests/ConsoleCapture.js +189 -0
  360. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  361. package/src/tests/browserTests.js +597 -0
  362. package/src/tests/debugBoolean.js +225 -0
  363. package/src/tests/partFiles/badBoolean.json +957 -0
  364. package/src/tests/partFiles/extrudeTest.json +88 -0
  365. package/src/tests/partFiles/filletFail.json +58 -0
  366. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  367. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  368. package/src/tests/test_boolean_subtract.js +27 -0
  369. package/src/tests/test_chamfer.js +17 -0
  370. package/src/tests/test_extrudeFace.js +24 -0
  371. package/src/tests/test_fillet.js +17 -0
  372. package/src/tests/test_fillet_nonClosed.js +45 -0
  373. package/src/tests/test_filletsMoreDifficult.js +46 -0
  374. package/src/tests/test_history_features_basic.js +149 -0
  375. package/src/tests/test_hole.js +282 -0
  376. package/src/tests/test_mirror.js +16 -0
  377. package/src/tests/test_offsetShellGrouping.js +85 -0
  378. package/src/tests/test_plane.js +4 -0
  379. package/src/tests/test_primitiveCone.js +11 -0
  380. package/src/tests/test_primitiveCube.js +7 -0
  381. package/src/tests/test_primitiveCylinder.js +8 -0
  382. package/src/tests/test_primitivePyramid.js +9 -0
  383. package/src/tests/test_primitiveSphere.js +17 -0
  384. package/src/tests/test_primitiveTorus.js +21 -0
  385. package/src/tests/test_pushFace.js +126 -0
  386. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  387. package/src/tests/test_sheetMetal_features.js +80 -0
  388. package/src/tests/test_sketch_openLoop.js +45 -0
  389. package/src/tests/test_solidMetrics.js +58 -0
  390. package/src/tests/test_stlLoader.js +1889 -0
  391. package/src/tests/test_sweepFace.js +55 -0
  392. package/src/tests/test_tube.js +45 -0
  393. package/src/tests/test_tube_closedLoop.js +67 -0
  394. package/src/tests/tests.js +493 -0
  395. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  396. package/src/tools/dialogCapturePageFactory.js +227 -0
  397. package/src/tools/featureDialogCapturePage.js +47 -0
  398. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  399. package/src/utils/axisHelpers.js +99 -0
  400. package/src/utils/deepClone.js +69 -0
  401. package/src/utils/geometryTolerance.js +37 -0
  402. package/src/utils/normalizeTypeString.js +8 -0
  403. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,1414 @@
1
+ "use strict";
2
+
3
+ import { SelectionFilter } from './SelectionFilter.js';
4
+ import * as THREE from 'three';
5
+ // Use hybrid translate+rotate gizmo used by the Viewer
6
+ import { CombinedTransformControls } from './controls/CombinedTransformControls.js';
7
+ import { getWidgetRenderer } from './featureDialogWidgets/index.js';
8
+ import { normalizeReferenceList, normalizeReferenceName } from './featureDialogWidgets/utils.js';
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
17
+ // SchemaForm: dark-mode, framework-free, ES module UI generator for schema-driven dialogs.
18
+ // - Renders inputs from a schema and keeps a provided `params` object in sync.
19
+ // - refreshFromParams() updates inputs when params are changed elsewhere.
20
+ // - Supports feature dialogs and annotation dialogs with shared widget implementations.
21
+ // - Special: type === "reference_selection" uses a scene-driven picker instead of a text box.
22
+ export class SchemaForm {
23
+ // Track a single globally-active reference selection input across all instances
24
+ static __activeRefInput = null;
25
+ static __setGlobalActiveRefInput(el) {
26
+ try {
27
+ // If another input was active, clear its visual + attribute
28
+ const prev = SchemaForm.__activeRefInput;
29
+ if (prev && prev !== el) {
30
+ try { prev.style.filter = 'none'; } catch (_) { }
31
+ try { prev.removeAttribute('active-reference-selection'); } catch (_) { }
32
+ }
33
+ } catch (_) { }
34
+ SchemaForm.__activeRefInput = el || null;
35
+ }
36
+
37
+ // Track a single globally-active transform controls session across all instances
38
+ static __activeXform = {
39
+ owner: null,
40
+ key: null,
41
+ inputEl: null,
42
+ wrapEl: null,
43
+ target: null,
44
+ controls: null,
45
+ viewer: null,
46
+ group: null,
47
+ controlsChangeHandler: null,
48
+ captureHandlers: null,
49
+ stepId: null,
50
+ valueAdapter: null,
51
+ baseTransform: null,
52
+ };
53
+ static __stopGlobalActiveXform() {
54
+ const s = SchemaForm.__activeXform;
55
+ if (!s || !s.controls) return;
56
+ try {
57
+ // Detach and dispose controls
58
+ s.controls.detach();
59
+ if (s.viewer && s.viewer.scene) {
60
+ try { if (s.controls && s.controls.isObject3D) s.viewer.scene.remove(s.controls); } catch (_) { }
61
+ try { if (s.controls && s.controls.__helper && s.controls.__helper.isObject3D) s.viewer.scene.remove(s.controls.__helper); } catch (_) { }
62
+ try { if (s.group && s.group.isObject3D) s.viewer.scene.remove(s.group); } catch (_) { }
63
+ }
64
+ try { s.controls.dispose(); } catch (_) { }
65
+ } catch (_) { }
66
+ try {
67
+ // Remove any capture-phase event listeners installed during activation
68
+ const h = s.captureHandlers;
69
+ if (h && h.canvas && h.onDownCapture) {
70
+ h.canvas.removeEventListener('pointerdown', h.onDownCapture, true);
71
+ }
72
+ if (h && h.win && h.onUpCapture) {
73
+ h.win.removeEventListener('pointerup', h.onUpCapture, true);
74
+ }
75
+ } catch (_) { }
76
+ try {
77
+ if (s && s.viewer && s.viewer.controls && s.controlsChangeHandler && typeof s.viewer.controls.removeEventListener === 'function') {
78
+ s.viewer.controls.removeEventListener('change', s.controlsChangeHandler);
79
+ }
80
+ } catch (_) { }
81
+ try {
82
+ // Remove target object
83
+ if (s.viewer && s.viewer.scene && s.target) s.viewer.scene.remove(s.target);
84
+ } catch (_) { }
85
+ try { if (window.__BREP_activeXform) window.__BREP_activeXform = null; } catch (_) { }
86
+ try {
87
+ // Restore camera controls
88
+ if (s.viewer && s.viewer.controls) s.viewer.controls.enabled = true;
89
+ } catch (_) { }
90
+ try {
91
+ // Clear highlight
92
+ if (s.inputEl) s.inputEl.removeAttribute('active-transform');
93
+ const wrap = s.wrapEl;
94
+ if (wrap) wrap.classList.remove('ref-active');
95
+ } catch (_) { }
96
+ SchemaForm.__activeXform = {
97
+ owner: null,
98
+ key: null,
99
+ stepId: null,
100
+ inputEl: null,
101
+ wrapEl: null,
102
+ target: null,
103
+ controls: null,
104
+ viewer: null,
105
+ group: null,
106
+ captureHandlers: null,
107
+ controlsChangeHandler: null,
108
+ valueAdapter: null,
109
+ baseTransform: null,
110
+ };
111
+ }
112
+
113
+ static getActiveTransformState() {
114
+ return SchemaForm.__activeXform;
115
+ }
116
+
117
+ static getActiveReferenceInput() {
118
+ return SchemaForm.__activeRefInput;
119
+ }
120
+
121
+ get activeTransform() {
122
+ return SchemaForm.__activeXform;
123
+ }
124
+
125
+ get activeReferenceInput() {
126
+ return SchemaForm.__activeRefInput;
127
+ }
128
+
129
+ isTransformSessionActiveFor(inputEl) {
130
+ const active = SchemaForm.__activeXform;
131
+ return Boolean(active && active.inputEl === inputEl);
132
+ }
133
+
134
+ setActiveTransformMode(inputEl, mode) {
135
+ const active = SchemaForm.__activeXform;
136
+ if (!active || active.inputEl !== inputEl || !active.controls) return;
137
+ try { active.controls.setMode(mode); } catch (_) { }
138
+ }
139
+
140
+ stopTransformSessionIfOwnedByThis() {
141
+ const active = SchemaForm.__activeXform;
142
+ if (active && active.owner === this) {
143
+ SchemaForm.__stopGlobalActiveXform();
144
+ }
145
+ }
146
+ /**
147
+ * @param {Object} schema - e.g. { sizeX: {type:'number', default_value:'2*t', hint:'Width formula' }, ... }
148
+ * @param {Object} params - a live object to keep in sync with user edits
149
+ * @param {Object} [options]
150
+ * @param {(featureID:string|null)=>void} [options.onChange] - Callback fired on any field change
151
+ */
152
+ constructor(schema, params, options = {}) {
153
+ if (!schema || typeof schema !== 'object') throw new Error('schema must be an object');
154
+ if (!params || typeof params !== 'object') throw new Error('params must be an object');
155
+
156
+ this.schema = schema;
157
+ this.params = params;
158
+ this.options = options;
159
+ this._useShadowDOM = options && Object.prototype.hasOwnProperty.call(options, 'useShadowDOM')
160
+ ? options.useShadowDOM !== false
161
+ : true;
162
+ this._inputs = new Map();
163
+ this._widgets = new Map();
164
+ this._skipDefaultRefresh = new Set();
165
+ this._excludedKeys = new Set(['id', 'featureID']); // exclude from defaults & rendering
166
+ if (Array.isArray(options.excludeKeys)) {
167
+ for (const key of options.excludeKeys) {
168
+ if (typeof key === 'string' && key.length) this._excludedKeys.add(key);
169
+ }
170
+ }
171
+
172
+ this.uiElement = document.createElement('div');
173
+ if (!this._useShadowDOM) {
174
+ this.uiElement.classList.add('schema-form-host');
175
+ }
176
+ this._shadow = this._useShadowDOM
177
+ ? this.uiElement.attachShadow({ mode: 'open' })
178
+ : this.uiElement;
179
+
180
+ this._shadow.appendChild(this._makeStyle());
181
+ this._panel = document.createElement('div');
182
+ this._panel.className = 'panel';
183
+ this._shadow.appendChild(this._panel);
184
+
185
+ this._fieldsWrap = document.createElement('div');
186
+ this._fieldsWrap.className = 'fields';
187
+ this._panel.appendChild(this._fieldsWrap);
188
+
189
+ this._renderAllFields();
190
+
191
+ // Deactivate reference selection when focusing or clicking into any other control
192
+ const stopIfOtherControl = (target) => {
193
+ try {
194
+ // If the active input is not the current focus target, stop selection
195
+ const active = SchemaForm.__activeRefInput || null;
196
+ if (!active) return;
197
+ if (target === active) return;
198
+ // If target is inside the same active element (e.g., clicking within the input), skip
199
+ if (target && typeof target.closest === 'function') {
200
+ if (target.closest('[active-reference-selection]')) return;
201
+ }
202
+ this._stopActiveReferenceSelection();
203
+ } catch (_) { }
204
+ try {
205
+ // Close active transform session if clicking outside its wrapper; commit changes
206
+ const s = SchemaForm.__activeXform;
207
+ if (s && s.owner === this) {
208
+ if (!(target && typeof target.closest === 'function' && target.closest('.transform-wrap'))) {
209
+ const val = this.params[s.key];
210
+ SchemaForm.__stopGlobalActiveXform();
211
+ this._emitParamsChange(s.key, val);
212
+ }
213
+ }
214
+ } catch (_) { }
215
+ };
216
+ // Capture focus changes within this form
217
+ this._shadow.addEventListener('focusin', (ev) => {
218
+ stopIfOtherControl(ev.target);
219
+ }, true);
220
+ // Also capture mouse interactions to be safe
221
+ this._shadow.addEventListener('mousedown', (ev) => {
222
+ stopIfOtherControl(ev.target);
223
+ }, true);
224
+ }
225
+
226
+ destroy() {
227
+ // If this form owns the active reference selector, clear it before tearing down
228
+ try {
229
+ const activeRef = (typeof SchemaForm.getActiveReferenceInput === 'function')
230
+ ? SchemaForm.getActiveReferenceInput()
231
+ : (SchemaForm.__activeRefInput || null);
232
+ const ownsActiveRef = activeRef
233
+ && (
234
+ (this._shadow && typeof this._shadow.contains === 'function' && this._shadow.contains(activeRef))
235
+ || (this.uiElement && typeof this.uiElement.contains === 'function' && this.uiElement.contains(activeRef))
236
+ || (typeof activeRef.getRootNode === 'function' && activeRef.getRootNode && activeRef.getRootNode() === this._shadow)
237
+ );
238
+ if (ownsActiveRef) this._stopActiveReferenceSelection();
239
+ } catch (_) { /* ignore */ }
240
+
241
+ // Clean up any active transform session owned by this instance
242
+ try {
243
+ const s = SchemaForm.__activeXform;
244
+ if (s && s.owner === this) SchemaForm.__stopGlobalActiveXform();
245
+ } catch (_) { }
246
+ for (const widget of this._widgets.values()) {
247
+ if (widget && typeof widget.destroy === 'function') {
248
+ try { widget.destroy(); } catch (_) { /* ignore widget destroy errors */ }
249
+ }
250
+ }
251
+ this._widgets.clear();
252
+ }
253
+
254
+ /** Returns the live params object (already kept in sync). */
255
+ getParams() {
256
+ return this.params;
257
+ }
258
+
259
+ /** Programmatically refresh input widgets from the current params object. */
260
+ refreshFromParams() {
261
+ for (const [key, widget] of this._widgets.entries()) {
262
+ if (widget && typeof widget.refreshFromParams === 'function') {
263
+ try {
264
+ widget.refreshFromParams(this.params[key], {
265
+ ui: this,
266
+ key,
267
+ def: this.schema[key] || {},
268
+ params: this.params,
269
+ });
270
+ } catch (_) { }
271
+ }
272
+ }
273
+
274
+ for (const [key, el] of this._inputs.entries()) {
275
+ const def = this.schema[key] || {};
276
+ if (this._skipDefaultRefresh.has(key)) {
277
+ continue;
278
+ }
279
+ const v = this._pickInitialValue(key, def);
280
+ // Special composite types handle their own refresh
281
+ if (def && def.type === 'boolean_operation') {
282
+ const row = this._fieldsWrap.querySelector(`[data-key="${key}"]`);
283
+ const select = row ? row.querySelector('select[data-role="bool-op"]') : null;
284
+ if (select) {
285
+ const opVal = (v && typeof v === 'object') ? (v.operation) : null;
286
+ select.value = opVal ? String(opVal) : 'NONE';
287
+ }
288
+ const chips = row ? row.querySelector('.ref-chips') : null;
289
+ const targets = (v && typeof v === 'object' && Array.isArray(v.targets)) ? v.targets : [];
290
+ if (chips) this._renderChips(chips, key, targets);
291
+ continue;
292
+ }
293
+ this._setInputValue(el, def.type, v);
294
+
295
+ // If this is a reference selection, refresh custom UI
296
+ if (def && def.type === 'reference_selection') {
297
+ const row = this._fieldsWrap.querySelector(`[data-key="${key}"]`);
298
+ if (def.multiple) {
299
+ const normalized = normalizeReferenceList(Array.isArray(v) ? v : []);
300
+ this.params[key] = normalized;
301
+ const chips = row ? row.querySelector('.ref-chips') : null;
302
+ if (chips) this._renderChips(chips, key, normalized);
303
+ } else {
304
+ const display = row ? row.querySelector('.ref-single-display') : null;
305
+ const normalized = normalizeReferenceName(v);
306
+ this.params[key] = normalized ?? null;
307
+ if (display) {
308
+ const label = display.querySelector('.ref-single-label');
309
+ const placeholder = display.dataset?.placeholder || 'Click then select in scene…';
310
+ if (label) label.textContent = normalized || placeholder;
311
+ else display.textContent = normalized || placeholder;
312
+ const clearBtn = display.querySelector('.ref-chip-remove');
313
+ if (clearBtn) clearBtn.style.visibility = normalized ? 'visible' : 'hidden';
314
+ }
315
+ }
316
+ continue;
317
+ }
318
+
319
+ // Transform widget: refresh info line
320
+ if (def && def.type === 'transform') {
321
+ const row = this._fieldsWrap.querySelector(`[data-key="${key}"]`);
322
+ const info = row ? row.querySelector('.transform-info') : null;
323
+ if (info) {
324
+ const fmt = (n) => {
325
+ const x = Number(n);
326
+ if (!Number.isFinite(x)) return '0';
327
+ const a = Math.abs(x);
328
+ const prec = a >= 100 ? 0 : (a >= 10 ? 1 : 2);
329
+ return String(x.toFixed(prec));
330
+ };
331
+ const p = Array.isArray(v?.position) ? v.position : [0, 0, 0];
332
+ const r = Array.isArray(v?.rotationEuler) ? v.rotationEuler : [0, 0, 0];
333
+ info.textContent = `pos(${fmt(p[0])}, ${fmt(p[1])}, ${fmt(p[2])}) rot(${fmt(r[0])}, ${fmt(r[1])}, ${fmt(r[2])})`;
334
+ }
335
+ continue;
336
+ }
337
+ }
338
+ }
339
+
340
+ // --- Internal: rendering & behavior ---------------------------------------
341
+
342
+ _renderAllFields() {
343
+ // Ensure params has defaults for missing keys (without clobbering provided values)
344
+ for (const key in this.schema) {
345
+ if (!Object.prototype.hasOwnProperty.call(this.schema, key)) continue;
346
+ const defRaw = this.schema[key];
347
+ const def = (defRaw && typeof defRaw === 'object') ? defRaw : {};
348
+ if (this._excludedKeys.has(key)) continue;
349
+ if (!(key in this.params)) {
350
+ const raw = ('default_value' in def) ? def.default_value : this._defaultForType(def.type);
351
+ this.params[key] = this._cloneDefault(raw);
352
+ }
353
+ }
354
+
355
+ this._widgets.clear();
356
+ this._skipDefaultRefresh.clear();
357
+
358
+ // Build field rows
359
+ for (const key in this.schema) {
360
+ if (!Object.prototype.hasOwnProperty.call(this.schema, key)) continue;
361
+ const defRaw = this.schema[key];
362
+ const def = (defRaw && typeof defRaw === 'object') ? defRaw : {};
363
+ if (this._excludedKeys.has(key)) continue;
364
+
365
+ const row = document.createElement('div');
366
+ row.className = 'field-row';
367
+ row.dataset.key = key;
368
+
369
+ if (def.hint != null && def.hint !== '') {
370
+ row.setAttribute('title', String(def.hint));
371
+ }
372
+
373
+ const id = 'gfu_' + key + '_' + Math.random().toString(36).slice(2, 8);
374
+
375
+ const label = document.createElement('label');
376
+ label.className = 'label';
377
+ label.setAttribute('for', id);
378
+ // Allow schema to override the row label via `label`
379
+ label.textContent = String((def && def.label) ? def.label : this._prettyLabel(key));
380
+ row.appendChild(label);
381
+
382
+ const controlWrap = document.createElement('div');
383
+ controlWrap.className = 'control-wrap';
384
+
385
+ let inputEl;
386
+ let inputRegistered = true;
387
+
388
+ // Allow schema defs to supply inline renderer without touching global registry.
389
+ const renderer =
390
+ typeof def.renderWidget === 'function'
391
+ ? def.renderWidget
392
+ : typeof def.widgetRenderer === 'function'
393
+ ? def.widgetRenderer
394
+ : getWidgetRenderer(def.type);
395
+ const widget = renderer({
396
+ ui: this,
397
+ key,
398
+ def,
399
+ id,
400
+ controlWrap,
401
+ row,
402
+ }) || {};
403
+
404
+ inputEl = widget.inputEl;
405
+ if (typeof widget.inputRegistered === 'boolean') {
406
+ inputRegistered = widget.inputRegistered;
407
+ }
408
+
409
+ if (widget && typeof widget === 'object') {
410
+ this._widgets.set(key, widget);
411
+ // Custom widgets can opt out of default refresh handling.
412
+ if (widget.skipDefaultRefresh === true) {
413
+ this._skipDefaultRefresh.add(key);
414
+ }
415
+ }
416
+
417
+ if (!inputEl || !(inputEl instanceof HTMLElement)) {
418
+ inputRegistered = false;
419
+ const placeholder = document.createElement('div');
420
+ placeholder.className = 'control-placeholder';
421
+ placeholder.textContent = 'Control unavailable';
422
+ controlWrap.appendChild(placeholder);
423
+ } else if (!inputEl.parentNode) {
424
+ controlWrap.appendChild(inputEl);
425
+ }
426
+
427
+ row.appendChild(controlWrap);
428
+ this._fieldsWrap.appendChild(row);
429
+ if (inputRegistered && inputEl instanceof HTMLElement) {
430
+ this._inputs.set(key, inputEl);
431
+ }
432
+ }
433
+ }
434
+
435
+ activateField(key) {
436
+ const widget = this._widgets.get(key);
437
+ if (widget && typeof widget.activate === 'function') {
438
+ try { widget.activate(); } catch (_) { }
439
+ return true;
440
+ }
441
+ return false;
442
+ }
443
+
444
+ readFieldValue(key) {
445
+ const widget = this._widgets.get(key);
446
+ if (widget && typeof widget.readValue === 'function') {
447
+ try { return widget.readValue(); } catch (_) { }
448
+ }
449
+ return this.params[key];
450
+ }
451
+
452
+ _cloneDefault(val) {
453
+ if (val == null) return val;
454
+ if (Array.isArray(val)) return val.map(v => this._cloneDefault(v));
455
+ if (typeof val === 'object') {
456
+ const proto = Object.getPrototypeOf(val);
457
+ if (proto === Object.prototype || proto === null) {
458
+ const out = {};
459
+ for (const k of Object.keys(val)) out[k] = this._cloneDefault(val[k]);
460
+ return out;
461
+ }
462
+ }
463
+ return val;
464
+ }
465
+
466
+ // Public: Activate the first reference_selection input in this form (if any)
467
+ activateFirstReferenceSelection() {
468
+ try {
469
+ for (const key in this.schema) {
470
+ if (!Object.prototype.hasOwnProperty.call(this.schema, key)) continue;
471
+ const def = this.schema[key];
472
+ if (def && def.type === 'reference_selection') {
473
+ const inputEl = this._inputs.get(key);
474
+ if (inputEl) {
475
+ this._activateReferenceSelection(inputEl, def);
476
+ return true;
477
+ }
478
+ }
479
+ }
480
+ } catch (_) { }
481
+ return false;
482
+ }
483
+
484
+ _activateReferenceSelection(inputEl, def) {
485
+ // Clear any lingering scene selection so the new reference starts fresh
486
+ try {
487
+ const scene = this.options?.scene
488
+ || this.options?.viewer?.partHistory?.scene
489
+ || this.options?.viewer?.scene
490
+ || null;
491
+ if (scene) {
492
+ SchemaForm.__setGlobalActiveRefInput(null);
493
+ SelectionFilter.unselectAll(scene);
494
+ }
495
+ } catch (_) { }
496
+
497
+ // Ensure only one control is globally marked as active
498
+ SchemaForm.__setGlobalActiveRefInput(inputEl);
499
+
500
+ // Also clear any duplicates within this shadow root (defensive)
501
+ const clearLocal = (root) => {
502
+ if (!root || typeof root.querySelectorAll !== 'function') return;
503
+ root.querySelectorAll('[active-reference-selection="true"],[active-reference-selection=true]').forEach(el => {
504
+ if (el !== inputEl) {
505
+ try { el.style.filter = 'none'; } catch (_) { }
506
+ try { el.removeAttribute('active-reference-selection'); } catch (_) { }
507
+ try {
508
+ const wrap = el.closest('.ref-single-wrap, .ref-multi-wrap');
509
+ if (wrap) wrap.classList.remove('ref-active');
510
+ } catch (_) { }
511
+ }
512
+ });
513
+ };
514
+ clearLocal(this._shadow);
515
+
516
+ // Mark this control active with a recency timestamp for any external scanners
517
+ try { inputEl.dataset.activatedAt = String(Date.now()); } catch (_) { }
518
+ inputEl.style.filter = 'invert(1)';
519
+ inputEl.setAttribute('active-reference-selection', 'true');
520
+ try {
521
+ const wrap = inputEl.closest('.ref-single-wrap, .ref-multi-wrap');
522
+ if (wrap) wrap.classList.add('ref-active');
523
+ } catch (_) { }
524
+
525
+ // Apply selection filter from schema
526
+ SelectionFilter.stashAllowedSelectionTypes();
527
+ SelectionFilter.SetSelectionTypes(def.selectionFilter);
528
+ try { window.__BREP_activeRefInput = inputEl; } catch (_) { }
529
+ }
530
+
531
+ // Activate a TransformControls session for a transform widget
532
+ _activateTransformWidget({ inputEl, wrapEl, key, def, valueAdapter = null }) {
533
+ try { this._stopActiveReferenceSelection(); } catch (_) { }
534
+ // Toggle logic: if already active for this input, stop and hide
535
+ try {
536
+ const s = SchemaForm.__activeXform;
537
+ if (s && s.inputEl === inputEl) {
538
+ const currentVal = this.params[key];
539
+ SchemaForm.__stopGlobalActiveXform();
540
+ this._emitParamsChange(key, currentVal);
541
+ return;
542
+ }
543
+ // If a different transform is active, stop it before starting this one
544
+ if (s && s.inputEl !== inputEl) {
545
+ SchemaForm.__stopGlobalActiveXform();
546
+ }
547
+ } catch (_) { }
548
+
549
+ const viewer = this.options?.viewer || null;
550
+ if (!viewer || !viewer.scene || !viewer.camera || !viewer.renderer) return;
551
+
552
+ // (Toggle handled above)
553
+
554
+ const adapter = (valueAdapter && typeof valueAdapter === 'object') ? valueAdapter : null;
555
+ const ensureArray3 = (arr, fallback) => {
556
+ const out = Array.isArray(arr) ? arr.slice(0, 3) : [];
557
+ while (out.length < 3) out.push(fallback);
558
+ return out;
559
+ };
560
+ const ensureArray4 = (arr, fallback) => {
561
+ if (Array.isArray(arr) && arr.length >= 4) {
562
+ const vals = [];
563
+ for (let i = 0; i < 4; i++) {
564
+ const n = Number(arr[i]);
565
+ vals.push(Number.isFinite(n) ? n : (i === 3 ? 1 : 0));
566
+ }
567
+ return vals;
568
+ }
569
+ return fallback;
570
+ };
571
+ const sanitizeTRS = (value) => {
572
+ const obj = (value && typeof value === 'object') ? value : {};
573
+ return {
574
+ position: ensureArray3(obj.position, 0),
575
+ rotationEuler: ensureArray3(obj.rotationEuler, 0),
576
+ scale: ensureArray3(obj.scale, 1),
577
+ };
578
+ };
579
+ const sanitizeBase = (value) => {
580
+ const obj = (value && typeof value === 'object') ? value : {};
581
+ const base = {
582
+ position: ensureArray3(obj.position, 0),
583
+ rotationEuler: ensureArray3(obj.rotationEuler, 0),
584
+ quaternion: ensureArray4(obj.quaternion, null),
585
+ scale: ensureArray3(obj.scale, 1),
586
+ };
587
+ if (!base.quaternion) {
588
+ try {
589
+ const e = base.rotationEuler;
590
+ const q = new THREE.Quaternion().setFromEuler(new THREE.Euler(
591
+ THREE.MathUtils.degToRad(e[0] || 0),
592
+ THREE.MathUtils.degToRad(e[1] || 0),
593
+ THREE.MathUtils.degToRad(e[2] || 0),
594
+ 'XYZ'
595
+ ));
596
+ base.quaternion = [q.x, q.y, q.z, q.w];
597
+ } catch (_) {
598
+ base.quaternion = [0, 0, 0, 1];
599
+ }
600
+ }
601
+ return base;
602
+ };
603
+ const safeNumber = (v, fallback) => {
604
+ const n = Number(v);
605
+ return Number.isFinite(n) ? n : fallback;
606
+ };
607
+ const safeDiv = (num, denom) => {
608
+ const d = safeNumber(denom, 1);
609
+ if (Math.abs(d) < 1e-12) return safeNumber(num, 0);
610
+ return safeNumber(num, 0) / d;
611
+ };
612
+ const readBaseValue = () => {
613
+ if (adapter && typeof adapter.getBase === 'function') {
614
+ try { return sanitizeBase(adapter.getBase()); } catch (_) { return sanitizeBase(null); }
615
+ }
616
+ return sanitizeBase(null);
617
+ };
618
+ const readCurrentValue = () => {
619
+ if (adapter && typeof adapter.get === 'function') {
620
+ try { return sanitizeTRS(adapter.get()); } catch (_) { return sanitizeTRS(null); }
621
+ }
622
+ return sanitizeTRS(this._pickInitialValue(key, def));
623
+ };
624
+ const writeCurrentValue = (next) => {
625
+ const sanitized = sanitizeTRS(next);
626
+ if (adapter && typeof adapter.set === 'function') {
627
+ try { adapter.set(sanitized); } catch (_) { }
628
+ } else {
629
+ this.params[key] = sanitized;
630
+ }
631
+ return sanitized;
632
+ };
633
+ const base = readBaseValue();
634
+ const cur = readCurrentValue();
635
+ const combineWithBase = (baseTransform, deltaTransform) => {
636
+ const basePos = new THREE.Vector3(
637
+ safeNumber(baseTransform.position[0], 0),
638
+ safeNumber(baseTransform.position[1], 0),
639
+ safeNumber(baseTransform.position[2], 0),
640
+ );
641
+ const baseQuat = new THREE.Quaternion().fromArray(baseTransform.quaternion);
642
+ const baseScale = new THREE.Vector3(
643
+ safeNumber(baseTransform.scale[0], 1),
644
+ safeNumber(baseTransform.scale[1], 1),
645
+ safeNumber(baseTransform.scale[2], 1),
646
+ );
647
+
648
+ const deltaPos = new THREE.Vector3(
649
+ safeNumber(deltaTransform.position[0], 0),
650
+ safeNumber(deltaTransform.position[1], 0),
651
+ safeNumber(deltaTransform.position[2], 0),
652
+ );
653
+ const deltaQuat = new THREE.Quaternion().setFromEuler(new THREE.Euler(
654
+ THREE.MathUtils.degToRad(safeNumber(deltaTransform.rotationEuler[0], 0)),
655
+ THREE.MathUtils.degToRad(safeNumber(deltaTransform.rotationEuler[1], 0)),
656
+ THREE.MathUtils.degToRad(safeNumber(deltaTransform.rotationEuler[2], 0)),
657
+ 'XYZ',
658
+ ));
659
+ const deltaScale = new THREE.Vector3(
660
+ safeNumber(deltaTransform.scale[0], 1),
661
+ safeNumber(deltaTransform.scale[1], 1),
662
+ safeNumber(deltaTransform.scale[2], 1),
663
+ );
664
+
665
+ const absPos = basePos.clone().add(deltaPos);
666
+ const absQuat = baseQuat.clone().multiply(deltaQuat);
667
+ const absScale = baseScale.clone().multiply(deltaScale);
668
+ return { position: absPos, quaternion: absQuat, scale: absScale };
669
+ };
670
+ const absolute = combineWithBase(base, cur);
671
+
672
+ const target = new THREE.Object3D();
673
+ try {
674
+ target.position.copy(absolute.position);
675
+ target.quaternion.copy(absolute.quaternion);
676
+ target.scale.copy(absolute.scale);
677
+ } catch (_) { }
678
+ viewer.scene.add(target);
679
+
680
+ const TCctor = CombinedTransformControls;
681
+ if (!TCctor) {
682
+ console.warn('[TransformControls] CombinedTransformControls not available; skipping gizmo.');
683
+ return;
684
+ }
685
+ const tc = new TCctor(viewer.camera, viewer.renderer.domElement);
686
+ const desiredMode = (inputEl && inputEl.dataset && inputEl.dataset.xformMode) ? String(inputEl.dataset.xformMode) : 'translate';
687
+ const safeMode = (desiredMode === 'scale') ? 'translate' : desiredMode;
688
+ tc.setMode(safeMode);
689
+ // Newer three.js TransformControls emit mouseDown/mouseUp instead of dragging-changed
690
+ let __lastCommitAt = 0;
691
+ const commitTransform = () => {
692
+ const now = Date.now();
693
+ if (now - __lastCommitAt < 5) return; // dedupe if two events fire together
694
+ __lastCommitAt = now;
695
+ try {
696
+ const featureID = (this.params && Object.prototype.hasOwnProperty.call(this.params, 'featureID'))
697
+ ? this.params.featureID
698
+ : (this.params?.id ?? null);
699
+ if (typeof this.options.onChange === 'function') {
700
+ this.options.onChange(featureID);
701
+ }
702
+ } catch (_) { }
703
+ // After history re-runs (which clears the scene), re-add the gizmo and target so it stays active
704
+ try {
705
+ const addBack = () => {
706
+ try {
707
+ const activeState = SchemaForm.__activeXform;
708
+ if (!activeState) return;
709
+ if (activeState.owner !== this) return;
710
+ if (activeState.inputEl !== inputEl) return;
711
+ if (activeState.key !== key) return;
712
+ if (adapter && typeof adapter.stepId === 'string' && activeState.stepId && activeState.stepId !== adapter.stepId) return;
713
+ if (!viewer || !viewer.scene) return;
714
+ if (!tc || typeof tc.attach !== 'function') return;
715
+ if (target && target.isObject3D) { try { viewer.scene.add(target); } catch (_) { } }
716
+ const helper = (typeof tc.getHelper === 'function') ? tc.getHelper() : null;
717
+ if (helper && helper.isObject3D) { try { viewer.scene.add(helper); tc.__helper = helper; } catch (_) { } }
718
+ else if (tc && tc.isObject3D) { try { viewer.scene.add(tc); } catch (_) { } }
719
+ else if (tc.__fallbackGroup && tc.__fallbackGroup.isObject3D) { try { viewer.scene.add(tc.__fallbackGroup); } catch (_) { } }
720
+ try { if (typeof tc.attach === 'function') tc.attach(target); } catch (_) { }
721
+ try {
722
+ const m = (typeof tc.getMode === 'function') ? tc.getMode() : (tc.mode || 'translate');
723
+ if (typeof tc.setMode === 'function') tc.setMode(m);
724
+ } catch (_) { }
725
+ try { viewer.render && viewer.render(); } catch (_) { }
726
+ try { refreshOverlay(); } catch (_) { }
727
+ try { updateForCamera(); } catch (_) { }
728
+ } catch (_) { }
729
+ };
730
+ if (typeof requestAnimationFrame === 'function') requestAnimationFrame(addBack);
731
+ else setTimeout(addBack, 0);
732
+ } catch (_) { }
733
+ };
734
+ const markOverlay = (obj) => {
735
+ if (!obj || !obj.isObject3D) return;
736
+ const apply = (node) => {
737
+ try {
738
+ if (!node || !node.isObject3D) return;
739
+ const ud = node.userData || (node.userData = {});
740
+ if (ud.__brepOverlayHook) return;
741
+ const prev = node.onBeforeRender;
742
+ node.onBeforeRender = function (renderer, scene, camera, geometry, material, group) {
743
+ try { renderer.clearDepth(); } catch (_) { }
744
+ if (typeof prev === 'function') {
745
+ prev.call(this, renderer, scene, camera, geometry, material, group);
746
+ }
747
+ };
748
+ ud.__brepOverlayHook = true;
749
+ } catch (_) { }
750
+ };
751
+ apply(obj);
752
+ if (typeof obj.traverse === 'function') obj.traverse((child) => apply(child));
753
+ };
754
+
755
+ const refreshOverlay = () => {
756
+ try {
757
+ markOverlay(tc);
758
+ markOverlay(tc._gizmo);
759
+ markOverlay(tc._helper);
760
+ markOverlay(tc.gizmo);
761
+ markOverlay(tc.helper);
762
+ markOverlay(tc.__helper);
763
+ markOverlay(tc.__fallbackGroup);
764
+ } catch (_) { }
765
+ };
766
+
767
+ const updateForCamera = () => {
768
+ try {
769
+ if (typeof tc.update === 'function') tc.update();
770
+ else tc.updateMatrixWorld(true);
771
+ } catch (_) { }
772
+ refreshOverlay();
773
+ };
774
+ try { updateForCamera(); } catch (_) { }
775
+ try {
776
+ if (viewer?.controls && typeof viewer.controls.addEventListener === 'function') {
777
+ viewer.controls.addEventListener('change', updateForCamera);
778
+ }
779
+ } catch (_) { }
780
+
781
+ try { tc.addEventListener('mouseDown', () => { try { if (viewer.controls) viewer.controls.enabled = false; } catch (_) { } refreshOverlay(); }); } catch (_) { }
782
+ try { tc.addEventListener('mouseUp', () => { try { if (viewer.controls) viewer.controls.enabled = true; } catch (_) { } commitTransform(); refreshOverlay(); }); } catch (_) { }
783
+ // Backward/compat: older builds fire dragging-changed
784
+ try {
785
+ tc.addEventListener('dragging-changed', (ev) => {
786
+ try { if (viewer.controls) viewer.controls.enabled = !ev.value; } catch (_) { }
787
+ if (!ev.value) commitTransform();
788
+ refreshOverlay();
789
+ });
790
+ } catch (_) { }
791
+
792
+ const updateParamFromTarget = () => {
793
+ const basePosVec = new THREE.Vector3(
794
+ safeNumber(base.position[0], 0),
795
+ safeNumber(base.position[1], 0),
796
+ safeNumber(base.position[2], 0),
797
+ );
798
+ const relPosVec = new THREE.Vector3(target.position.x, target.position.y, target.position.z).sub(basePosVec);
799
+
800
+ const baseQuatObj = new THREE.Quaternion().fromArray(base.quaternion);
801
+ const relQuat = baseQuatObj.clone().invert().multiply(target.quaternion.clone());
802
+ const relEuler = new THREE.Euler().setFromQuaternion(relQuat, 'XYZ');
803
+
804
+ const baseScaleVec = new THREE.Vector3(
805
+ safeNumber(base.scale[0], 1),
806
+ safeNumber(base.scale[1], 1),
807
+ safeNumber(base.scale[2], 1),
808
+ );
809
+ const relScaleVec = new THREE.Vector3(
810
+ safeDiv(target.scale.x, baseScaleVec.x),
811
+ safeDiv(target.scale.y, baseScaleVec.y),
812
+ safeDiv(target.scale.z, baseScaleVec.z),
813
+ );
814
+
815
+ const next = {
816
+ position: [relPosVec.x, relPosVec.y, relPosVec.z],
817
+ rotationEuler: [
818
+ THREE.MathUtils.radToDeg(relEuler.x),
819
+ THREE.MathUtils.radToDeg(relEuler.y),
820
+ THREE.MathUtils.radToDeg(relEuler.z)
821
+ ],
822
+ scale: [relScaleVec.x, relScaleVec.y, relScaleVec.z],
823
+ };
824
+ const stored = writeCurrentValue(next);
825
+ if (!adapter) {
826
+ try {
827
+ const row = this._fieldsWrap.querySelector(`[data-key="${key}"]`);
828
+ const info = row ? row.querySelector('.transform-info') : null;
829
+ if (info) {
830
+ const fmt = (n) => {
831
+ const x = Number(n);
832
+ if (!Number.isFinite(x)) return '0';
833
+ const a = Math.abs(x);
834
+ const prec = a >= 100 ? 0 : (a >= 10 ? 1 : 2);
835
+ return String(x.toFixed(prec));
836
+ };
837
+ info.textContent = `pos(${fmt(stored.position[0])}, ${fmt(stored.position[1])}, ${fmt(stored.position[2])}) rot(${fmt(stored.rotationEuler[0])}, ${fmt(stored.rotationEuler[1])}, ${fmt(stored.rotationEuler[2])})`;
838
+ }
839
+ try {
840
+ const pairs = [
841
+ ['.tf-pos-x', stored.position[0]],
842
+ ['.tf-pos-y', stored.position[1]],
843
+ ['.tf-pos-z', stored.position[2]],
844
+ ['.tf-rot-x', stored.rotationEuler[0]],
845
+ ['.tf-rot-y', stored.rotationEuler[1]],
846
+ ['.tf-rot-z', stored.rotationEuler[2]],
847
+ ];
848
+ for (const [sel, val] of pairs) {
849
+ const el = row ? row.querySelector(sel) : null;
850
+ if (el) this._setInputValue(el, 'number', val);
851
+ }
852
+ } catch (_) { }
853
+ } catch (_) { }
854
+ }
855
+ };
856
+ tc.addEventListener('change', (ev) => { updateParamFromTarget(ev); refreshOverlay(); });
857
+ // Fallback commit for cases where mouseUp/dragging-changed are unreliable (some builds)
858
+ try { tc.addEventListener('objectChange', () => { try { if (!tc.dragging) commitTransform(); } catch (_) { } refreshOverlay(); }); } catch (_) { }
859
+
860
+ // Expose an isOver helper for Viewer to suppress its own handlers when interacting with gizmo
861
+ const isOver = (ev) => {
862
+ try {
863
+ const canvas = viewer.renderer.domElement;
864
+ const rect = canvas.getBoundingClientRect();
865
+ const x = (ev.clientX - rect.left) / rect.width; // 0..1
866
+ const y = (ev.clientY - rect.top) / rect.height; // 0..1
867
+ // Use viewer helper for consistent NDC mapping
868
+ const ndc = (typeof viewer._getPointerNDC === 'function')
869
+ ? viewer._getPointerNDC({ clientX: ev.clientX, clientY: ev.clientY })
870
+ : new THREE.Vector2(x * 2 - 1, -(y * 2 - 1));
871
+ viewer.raycaster.setFromCamera(ndc, viewer.camera);
872
+ // Prefer precise picker meshes for the current mode; fallback to whole gizmo
873
+ const mode = (typeof tc.getMode === 'function') ? tc.getMode() : (tc.mode || desiredMode || 'translate');
874
+ const giz = tc._gizmo || tc.gizmo || null;
875
+ const pick = (giz && giz.picker) ? (giz.picker[mode] || giz.picker.translate || giz.picker.rotate) : null;
876
+ const pickRoot = pick || giz || tc.__fallbackGroup || null;
877
+ if (!pickRoot) return false;
878
+ const hits = viewer.raycaster.intersectObject(pickRoot, true) || [];
879
+ return hits.length > 0;
880
+ } catch (_) { return false; }
881
+ };
882
+ try {
883
+ window.__BREP_activeXform = {
884
+ controls: tc,
885
+ viewer,
886
+ isOver,
887
+ target,
888
+ group: tc.__fallbackGroup || (tc && tc.isObject3D ? tc : null),
889
+ updateForCamera,
890
+ };
891
+ } catch (_) { }
892
+
893
+ let addedToScene = false;
894
+ try { markOverlay(tc._gizmo); } catch (_) { }
895
+ try { markOverlay(tc._helper); } catch (_) { }
896
+ try { markOverlay(tc.gizmo); } catch (_) { }
897
+ try { markOverlay(tc.helper); } catch (_) { }
898
+
899
+ try {
900
+ // Preferred modern API: helper root on the controls
901
+ const helper = (typeof tc.getHelper === 'function') ? tc.getHelper() : null;
902
+ if (helper && helper.isObject3D) {
903
+ try { helper.userData = helper.userData || {}; helper.userData.excludeFromFit = true; } catch (_) { }
904
+ markOverlay(helper);
905
+ viewer.scene.add(helper); addedToScene = true; tc.__helper = helper;
906
+ }
907
+ else if (tc && tc.isObject3D) {
908
+ try { tc.userData = tc.userData || {}; tc.userData.excludeFromFit = true; } catch (_) { }
909
+ markOverlay(tc);
910
+ viewer.scene.add(tc); addedToScene = true;
911
+ }
912
+ } catch (_) { /* tolerate builds where controls aren't Object3D */ }
913
+ if (!addedToScene) {
914
+ // Fallback: try adding known internal object3D parts if present
915
+ try {
916
+ const group = new THREE.Group();
917
+ group.name = 'TransformControlsGroup';
918
+ const candidates = [tc?.gizmo, tc?._gizmo, tc?.picker, tc?._picker, tc?.helper, tc?._helper];
919
+ let attached = 0;
920
+ for (const cand of candidates) {
921
+ if (cand && cand.isObject3D) { try { group.add(cand); attached++; } catch (_) { } }
922
+ }
923
+ if (attached > 0) {
924
+ try { group.userData = group.userData || {}; group.userData.excludeFromFit = true; } catch (_) { }
925
+ markOverlay(group);
926
+ viewer.scene.add(group); addedToScene = true; tc.__fallbackGroup = group;
927
+ }
928
+ } catch (_) { /* ignore */ }
929
+ if (!addedToScene) {
930
+ // eslint-disable-next-line no-console
931
+ console.warn('[TransformControls] Could not add gizmo to scene (no Object3D found).');
932
+ }
933
+ }
934
+ try { tc.showX = true; tc.showY = true; tc.showZ = true; } catch (_) { }
935
+ try { tc.setSpace('world'); } catch (_) { }
936
+ try { tc.addEventListener('change', () => { try { viewer.render(); } catch (_) { } }); } catch (_) { }
937
+ try { tc.attach(target); markOverlay(tc); markOverlay(tc.__helper); markOverlay(tc.__fallbackGroup); } catch (_) { }
938
+
939
+ // Mark active
940
+ inputEl.setAttribute('active-transform', 'true');
941
+ try { wrapEl.classList.add('ref-active'); } catch (_) { }
942
+
943
+ SchemaForm.__activeXform = {
944
+ owner: this,
945
+ key,
946
+ stepId: adapter && typeof adapter.stepId === 'string' ? adapter.stepId : null,
947
+ inputEl,
948
+ wrapEl,
949
+ target,
950
+ controls: tc,
951
+ viewer,
952
+ group: tc.__fallbackGroup || (tc && tc.isObject3D ? tc : null),
953
+ captureHandlers: null,
954
+ controlsChangeHandler: updateForCamera,
955
+ valueAdapter: adapter || null,
956
+ baseTransform: base,
957
+ };
958
+
959
+ // Install capture-phase listeners to disable ArcballControls early when pressing gizmo
960
+ try {
961
+ const canvas = viewer && viewer.renderer ? viewer.renderer.domElement : null;
962
+ if (canvas && typeof canvas.addEventListener === 'function') {
963
+ const onDownCapture = (ev) => {
964
+ try {
965
+ if (isOver(ev)) {
966
+ if (viewer && viewer.controls) viewer.controls.enabled = false;
967
+ }
968
+ } catch (_) { }
969
+ };
970
+ const onUpCapture = (ev) => {
971
+ // Re-enable controls on pointer release to be safe
972
+ try { if (viewer && viewer.controls) viewer.controls.enabled = true; } catch (_) { }
973
+ void ev;
974
+ };
975
+ canvas.addEventListener('pointerdown', onDownCapture, { passive: true, capture: true });
976
+ // Use window to ensure we catch release even if released off-canvas
977
+ window.addEventListener('pointerup', onUpCapture, { passive: true, capture: true });
978
+ SchemaForm.__activeXform.captureHandlers = { canvas, win: window, onDownCapture, onUpCapture };
979
+ }
980
+ } catch (_) { /* ignore */ }
981
+ }
982
+
983
+ _stopActiveTransformWidget() {
984
+ try { SchemaForm.__stopGlobalActiveXform(); } catch (_) { }
985
+ }
986
+
987
+
988
+ _stopActiveReferenceSelection() {
989
+ // Clear global active if it belongs to this instance
990
+ try {
991
+ if (SchemaForm.__activeRefInput) {
992
+ try { SchemaForm.__activeRefInput.style.filter = 'none'; } catch (_) { }
993
+ try { SchemaForm.__activeRefInput.removeAttribute('active-reference-selection'); } catch (_) { }
994
+ try {
995
+ const wrap = SchemaForm.__activeRefInput.closest('.ref-single-wrap, .ref-multi-wrap');
996
+ if (wrap) wrap.classList.remove('ref-active');
997
+ } catch (_) { }
998
+ }
999
+ } catch (_) { }
1000
+ SchemaForm.__activeRefInput = null;
1001
+ try { if (window.__BREP_activeRefInput === undefined || window.__BREP_activeRefInput === SchemaForm.__activeRefInput) window.__BREP_activeRefInput = null; } catch (_) { }
1002
+ SelectionFilter.restoreAllowedSelectionTypes();
1003
+ }
1004
+
1005
+ _renderChips(chipsWrap, key, values) {
1006
+ chipsWrap.textContent = '';
1007
+ const arr = Array.isArray(values) ? values : [];
1008
+ const normalizedValues = normalizeReferenceList(arr);
1009
+ const inputEl = (this._inputs && typeof this._inputs.get === 'function') ? this._inputs.get(key) : null;
1010
+ if (inputEl) {
1011
+ if (typeof inputEl.__updateSelectionMetadata === 'function') {
1012
+ try { inputEl.__updateSelectionMetadata(normalizedValues); } catch (_) { }
1013
+ } else if (inputEl.dataset && inputEl.dataset.multiple === 'true') {
1014
+ try { inputEl.dataset.selectedCount = String(normalizedValues.length); } catch (_) { }
1015
+ try { inputEl.dataset.selectedValues = JSON.stringify(normalizedValues); } catch (_) { }
1016
+ }
1017
+ }
1018
+ if (Array.isArray(this.params[key])) {
1019
+ this.params[key] = normalizedValues;
1020
+ } else if (this.params[key] && typeof this.params[key] === 'object' && Array.isArray(this.params[key].targets)) {
1021
+ this.params[key].targets = normalizedValues;
1022
+ }
1023
+ for (const name of normalizedValues) {
1024
+ const chip = document.createElement('span');
1025
+ chip.className = 'ref-chip';
1026
+
1027
+ const label = document.createElement('span');
1028
+ label.className = 'ref-chip-label';
1029
+ label.textContent = name;
1030
+ chip.appendChild(label);
1031
+
1032
+ // Hover highlight on chip hover
1033
+ chip.addEventListener('mouseenter', () => {
1034
+ try { SelectionFilter.setHoverByName(this.options?.scene || null, name); } catch (_) { }
1035
+ });
1036
+ chip.addEventListener('mouseleave', () => {
1037
+ try { SelectionFilter.clearHover(); } catch (_) { }
1038
+ });
1039
+
1040
+ const btn = document.createElement('span');
1041
+ btn.className = 'ref-chip-remove';
1042
+ btn.textContent = '✕';
1043
+ btn.title = 'Remove';
1044
+ btn.addEventListener('click', (ev) => {
1045
+ ev.stopPropagation();
1046
+ // Support both plain array params and object-with-targets
1047
+ let currentArrayRef = null;
1048
+ if (Array.isArray(this.params[key])) {
1049
+ currentArrayRef = this.params[key];
1050
+ } else if (this.params[key] && typeof this.params[key] === 'object' && Array.isArray(this.params[key].targets)) {
1051
+ currentArrayRef = this.params[key].targets;
1052
+ } else {
1053
+ // Initialize as array if nothing sensible exists
1054
+ this.params[key] = [];
1055
+ currentArrayRef = this.params[key];
1056
+ }
1057
+ const idx = currentArrayRef.indexOf(name);
1058
+ if (idx >= 0) currentArrayRef.splice(idx, 1);
1059
+ this._renderChips(chipsWrap, key, currentArrayRef);
1060
+ this._emitParamsChange(key, this.params[key]);
1061
+ try {
1062
+ if (typeof this.options.onReferenceChipRemove === 'function') {
1063
+ this.options.onReferenceChipRemove(name, key);
1064
+ }
1065
+ } catch (_) { }
1066
+ });
1067
+ chip.appendChild(btn);
1068
+
1069
+ chipsWrap.appendChild(chip);
1070
+ }
1071
+ if (normalizedValues.length === 0) {
1072
+ const hint = document.createElement('span');
1073
+ hint.className = 'ref-chip';
1074
+ hint.style.opacity = '0.6';
1075
+ let hintText = 'Click then pick items in scene';
1076
+ if (inputEl && inputEl.dataset) {
1077
+ const minAttr = Number(inputEl.dataset.minSelections);
1078
+ if (Number.isFinite(minAttr) && minAttr > 0) {
1079
+ hintText = `Select at least ${minAttr} item${minAttr === 1 ? '' : 's'}`;
1080
+ }
1081
+ }
1082
+ hint.textContent = hintText;
1083
+ chipsWrap.appendChild(hint);
1084
+ } else if (inputEl && inputEl.dataset) {
1085
+ const minAttr = Number(inputEl.dataset.minSelections);
1086
+ if (Number.isFinite(minAttr) && minAttr > 0 && normalizedValues.length < minAttr) {
1087
+ const hint = document.createElement('span');
1088
+ hint.className = 'ref-chip';
1089
+ hint.style.opacity = '0.6';
1090
+ hint.textContent = `Need ${minAttr - normalizedValues.length} more`;
1091
+ chipsWrap.appendChild(hint);
1092
+ }
1093
+ }
1094
+ }
1095
+
1096
+ _emitParamsChange(key, value) {
1097
+ // Suppress auto-run if a transform editing session is active on this form
1098
+ try {
1099
+ const s = SchemaForm.__activeXform;
1100
+ if (s && s.owner === this) return;
1101
+ } catch (_) { }
1102
+ if (typeof this.options.onChange === 'function') {
1103
+ const featureID = (this.params && Object.prototype.hasOwnProperty.call(this.params, 'featureID'))
1104
+ ? this.params.featureID
1105
+ : (this.params?.id ?? null);
1106
+ const details = { key, value, params: this.params, form: this };
1107
+ try {
1108
+ this.options.onChange(featureID, details);
1109
+ } catch (error) {
1110
+ // eslint-disable-next-line no-console
1111
+ console.log(error);
1112
+ }
1113
+ }
1114
+ }
1115
+
1116
+ _pickInitialValue(key, def) {
1117
+ if (this.params[key] !== undefined && this.params[key] !== null) return this.params[key];
1118
+ if (Object.prototype.hasOwnProperty.call(def, 'default_value')) return def.default_value;
1119
+ return this._defaultForType(def.type);
1120
+ }
1121
+
1122
+ _defaultForType(type) {
1123
+ switch (type) {
1124
+ case 'boolean': return false;
1125
+ case 'options': return '';
1126
+ case 'reference_selection': return null;
1127
+ case 'transform': return { position: [0, 0, 0], rotationEuler: [0, 0, 0], scale: [1, 1, 1] };
1128
+ case 'vec3': return [0, 0, 0];
1129
+ default: return '';
1130
+ }
1131
+ }
1132
+
1133
+ _setInputValue(el, type, value) {
1134
+ switch (type) {
1135
+ case 'boolean':
1136
+ el.checked = Boolean(value);
1137
+ break;
1138
+ case 'number': {
1139
+ // Accept formulas or plain numbers. If the value is not purely numeric,
1140
+ // render the input as text so the expression is visible. Some inputs
1141
+ // force text rendering to avoid type switching.
1142
+ const rawStr = value == null ? '' : String(value);
1143
+ const numericLike = /^\s*[-+]?((\d+(?:\.\d*)?)|(\.\d+))(?:[eE][-+]?\d+)?\s*$/.test(rawStr);
1144
+ const forceText = el && el.dataset && el.dataset.forceText === 'true';
1145
+ try {
1146
+ if (!forceText && numericLike) {
1147
+ if (el.type !== 'number') el.type = 'number';
1148
+ // Re-apply numeric attributes if we previously toggled away
1149
+ if (el.dataset && el.dataset.step) el.step = el.dataset.step;
1150
+ if (el.dataset && el.dataset.min) el.min = el.dataset.min;
1151
+ if (el.dataset && el.dataset.max) el.max = el.dataset.max;
1152
+ } else {
1153
+ if (el.type !== 'text') el.type = 'text';
1154
+ }
1155
+ } catch (_) { /* ignore */ }
1156
+ // Limit programmatically-set numeric text to 6 decimal places.
1157
+ const format6 = (v) => {
1158
+ let n = Number(v);
1159
+ if (!Number.isFinite(n)) return rawStr;
1160
+ if (Math.abs(n) < 1e-12) n = 0; // avoid tiny scientific notation
1161
+ let s = n.toFixed(6);
1162
+ s = s.replace(/\.0+$/, ''); // trim trailing .000000
1163
+ s = s.replace(/(\.\d*?[1-9])0+$/, '$1'); // trim trailing zeros
1164
+ if (s === '-0') s = '0';
1165
+ return s;
1166
+ };
1167
+ el.value = numericLike ? format6(value) : rawStr;
1168
+ break;
1169
+ }
1170
+ case 'options': {
1171
+ const asStr = String(value == null ? '' : value);
1172
+ let has = false;
1173
+ for (let i = 0; i < el.options.length; i++) {
1174
+ if (el.options[i].value === asStr) { has = true; break; }
1175
+ }
1176
+ el.value = has ? asStr : (el.options[0] ? el.options[0].value : '');
1177
+ break;
1178
+ }
1179
+ case 'file': {
1180
+ // Update the info label adjacent to the button
1181
+ try {
1182
+ const wrap = el && el.parentNode ? el.parentNode : null;
1183
+ const info = wrap ? wrap.querySelector('.file-info') : null;
1184
+ if (info) {
1185
+ if (typeof value === 'string' && value.startsWith('data:') && value.includes(';base64,')) {
1186
+ const b64 = value.split(',')[1] || '';
1187
+ const size = Math.floor((b64.length * 3) / 4);
1188
+ info.textContent = `Loaded (${size} bytes)`;
1189
+ } else if (value && String(value).length) {
1190
+ info.textContent = `Loaded (${String(value).length} chars)`;
1191
+ } else {
1192
+ info.textContent = 'No file selected';
1193
+ }
1194
+ }
1195
+ } catch (_) { }
1196
+ break;
1197
+ }
1198
+ default:
1199
+ el.value = value == null ? '' : String(value);
1200
+ break;
1201
+ }
1202
+ }
1203
+
1204
+ _prettyLabel(key) {
1205
+ const withSpaces = String(key)
1206
+ .replace(/_/g, ' ')
1207
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
1208
+ .replace(/\s+/g, ' ')
1209
+ .trim();
1210
+ return withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
1211
+ }
1212
+
1213
+ _makeStyle() {
1214
+ const style = document.createElement('style');
1215
+ style.textContent = `
1216
+ :host, .schema-form-host, .panel {
1217
+ --bg: #0f1117;
1218
+ --bg-elev: #12141b;
1219
+ --border: #262b36;
1220
+ --text: #e6e6e6;
1221
+ --muted: #9aa4b2;
1222
+ --accent: #6ea8fe;
1223
+ --focus: #3b82f6;
1224
+ --input-bg: #0b0e14;
1225
+ --radius: 12px;
1226
+ --gap: 3px;
1227
+ color-scheme: dark;
1228
+ }
1229
+
1230
+ .panel {
1231
+ color: var(--text);
1232
+ background: transparent;
1233
+ border-radius: var(--radius);
1234
+ max-width: 100%;
1235
+ }
1236
+
1237
+ .fields {
1238
+ display: flex;
1239
+ flex-direction: column;
1240
+ gap: 12px;
1241
+ }
1242
+
1243
+ .field-row {
1244
+ display: grid;
1245
+ grid-template-columns: 1fr;
1246
+ gap: 6px;
1247
+ }
1248
+
1249
+ .label {
1250
+ color: var(--muted);
1251
+ }
1252
+
1253
+ .control-wrap { display: flex; flex-direction: column; gap: 6px; }
1254
+
1255
+ .input, .select {
1256
+
1257
+ background: var(--input-bg);
1258
+ color: var(--text);
1259
+ border: 1px solid var(--border);
1260
+ border-radius: 10px;
1261
+ padding: 8px 10px;
1262
+ outline: none;
1263
+ transition: border-color .15s ease, box-shadow .15s ease;
1264
+ width: 100%;
1265
+ box-sizing: border-box;
1266
+ }
1267
+ .number-input-wrap {
1268
+ position: relative;
1269
+ width: 100%;
1270
+ }
1271
+ .number-input {
1272
+ padding-right: 36px;
1273
+ }
1274
+ .number-stepper {
1275
+ position: absolute;
1276
+ top: 4px;
1277
+ bottom: 4px;
1278
+ right: 4px;
1279
+ width: 26px;
1280
+ display: flex;
1281
+ flex-direction: column;
1282
+ border-left: 1px solid var(--border);
1283
+ border-radius: 8px;
1284
+ overflow: hidden;
1285
+ background: rgba(255,255,255,.02);
1286
+ }
1287
+ .number-stepper-btn {
1288
+ appearance: none;
1289
+ border: 0;
1290
+ padding: 0;
1291
+ margin: 0;
1292
+ background: transparent;
1293
+ cursor: pointer;
1294
+ flex: 1 1 50%;
1295
+ display: flex;
1296
+ align-items: center;
1297
+ justify-content: center;
1298
+ }
1299
+ .number-stepper-btn::before {
1300
+ content: '';
1301
+ width: 0;
1302
+ height: 0;
1303
+ border-left: 4px solid transparent;
1304
+ border-right: 4px solid transparent;
1305
+ }
1306
+ .number-stepper-up { border-bottom: 1px solid var(--border); }
1307
+ .number-stepper-up::before { border-bottom: 6px solid var(--muted); }
1308
+ .number-stepper-down::before { border-top: 6px solid var(--muted); }
1309
+ .number-stepper-up:hover::before { border-bottom-color: var(--text); }
1310
+ .number-stepper-down:hover::before { border-top-color: var(--text); }
1311
+ .number-stepper-btn:active { background: rgba(255,255,255,.06); }
1312
+ textarea.input {
1313
+ resize: vertical;
1314
+ line-height: 1.4;
1315
+ min-height: 72px;
1316
+ font-family: inherit;
1317
+ }
1318
+ .btn {
1319
+ appearance: none;
1320
+ background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
1321
+ color: var(--text);
1322
+ border: 1px solid var(--border);
1323
+ border-radius: 10px;
1324
+ padding: 8px 12px;
1325
+ cursor: pointer;
1326
+ transition: border-color .15s ease, box-shadow .15s ease, transform .05s ease;
1327
+ }
1328
+ .btn.btn-slim { padding: 6px 10px; border-radius: 8px; font-size: 12px; }
1329
+ .btn.selected { border-color: var(--focus); color: #fff; }
1330
+ .btn:hover { border-color: var(--focus); box-shadow: 0 0 0 3px rgba(59,130,246,.15); }
1331
+ .btn:active { transform: translateY(1px); }
1332
+ .input:focus, .select:focus {
1333
+ border-color: var(--focus);
1334
+
1335
+ }
1336
+ .file-info { font-size: 12px; color: var(--muted); }
1337
+
1338
+ .checkbox {
1339
+ width: 18px; height: 18px;
1340
+ accent-color: var(--accent);
1341
+ }
1342
+
1343
+ .ref-select-placeholder {
1344
+ min-height: 36px;
1345
+ border: 1px dashed var(--border);
1346
+ border-radius: 10px;
1347
+ background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(255,255,255,.01));
1348
+ }
1349
+ /* Single reference display (replaces textbox) */
1350
+ .ref-single-wrap { display: block; }
1351
+ .ref-single-display {
1352
+ appearance: none;
1353
+ background: var(--input-bg);
1354
+ color: var(--text);
1355
+ border: 1px solid var(--border);
1356
+ border-radius: 10px;
1357
+ padding: 8px 10px;
1358
+ outline: none;
1359
+ cursor: pointer;
1360
+ user-select: none;
1361
+ min-height: 36px;
1362
+ display: flex;
1363
+ align-items: center;
1364
+ justify-content: space-between;
1365
+ gap: 8px;
1366
+ }
1367
+ .ref-single-label { flex: 1 1 auto; overflow-wrap: anywhere; text-align: left; }
1368
+ /* Active highlight for ref widgets */
1369
+ .ref-single-wrap.ref-active .ref-single-display,
1370
+ .ref-multi-wrap.ref-active .ref-chips {
1371
+ border-color: var(--focus);
1372
+ box-shadow: 0 0 0 3px rgba(59,130,246,.15);
1373
+ }
1374
+ /* Multi reference chips */
1375
+ .ref-multi-wrap { display: flex; flex-direction: column; gap: 6px; }
1376
+ .ref-chips { display: flex; flex-wrap: wrap; gap: 6px; padding: 4px; border: 1px dashed var(--border); border-radius: 10px; cursor: pointer; background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(255,255,255,.01)); max-width: 100%; }
1377
+ .ref-multi-wrap.ref-limit-reached .ref-chips { border-color: #f97316; animation: refLimitPulse 0.48s ease; }
1378
+ @keyframes refLimitPulse {
1379
+ 0% { box-shadow: 0 0 0 0 rgba(249,115,22,0.32); }
1380
+ 100% { box-shadow: 0 0 0 12px rgba(249,115,22,0); }
1381
+ }
1382
+ .ref-chip { display: inline-flex; align-items: center; gap: 6px; padding: 4px 8px; border-radius: 999px; background: #1a2030; border: 1px solid var(--border); font-size: 12px; max-width: 100%; }
1383
+ .ref-chip-label { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; word-break: break-word; white-space: normal; }
1384
+ .ref-chip-remove { color: var(--muted); cursor: pointer; flex: 0 0 auto; }
1385
+ .ref-chip-remove:hover { color: var(--danger); }
1386
+
1387
+ /* Transform widget */
1388
+ .transform-wrap { display: flex; flex-direction: column; gap: 8px; }
1389
+ .transform-modes { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
1390
+ .transform-info { font-size: 12px; color: var(--muted); }
1391
+ .transform-details { display: none; }
1392
+ .transform-wrap.ref-active .transform-details { display: block; }
1393
+ .transform-grid { display: flex; flex-direction: column; gap: 6px; }
1394
+ .transform-row { display: grid; grid-template-columns: auto 1fr; align-items: center; gap: 8px; }
1395
+ .transform-label { color: var(--muted); font-size: 12px; }
1396
+ .transform-inputs { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 6px; }
1397
+ .transform-input { padding: 6px 8px; }
1398
+ .transform-wrap.ref-active .btn { border-color: var(--focus); box-shadow: 0 0 0 3px rgba(59,130,246,.15); }
1399
+
1400
+ .multi-transform-wrap { display: flex; flex-direction: column; gap: 10px; }
1401
+ .mt-list { display: flex; flex-direction: column; gap: 10px; }
1402
+ .mt-item { display: flex; flex-direction: column; gap: 8px; padding: 10px; border-radius: 12px; border: 1px solid var(--border); background: linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,255,255,.01)); }
1403
+ .mt-item-header { display: flex; justify-content: space-between; align-items: center; font-weight: 500; }
1404
+ .mt-item-actions { display: inline-flex; gap: 4px; }
1405
+ .mt-item-actions .btn-icon { font-size: 12px; line-height: 1; padding: 4px 6px; }
1406
+ .mt-row { display: grid; grid-template-columns: auto 1fr; gap: 8px; align-items: center; }
1407
+ .mt-row-label { font-size: 12px; color: var(--muted); }
1408
+ .mt-row-inputs { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 6px; }
1409
+ .mt-number { padding: 6px 8px; }
1410
+ .control-placeholder { padding: 8px; font-size: 12px; color: var(--muted); border: 1px dashed var(--border); border-radius: 10px; background: rgba(15,23,42,0.35); }
1411
+ `;
1412
+ return style;
1413
+ }
1414
+ }