brep-io-kernel 1.0.0-ci.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +154 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/package.json +58 -0
  5. package/src/BREP/AssemblyComponent.js +42 -0
  6. package/src/BREP/BREP.js +43 -0
  7. package/src/BREP/BetterSolid.js +805 -0
  8. package/src/BREP/Edge.js +103 -0
  9. package/src/BREP/Extrude.js +403 -0
  10. package/src/BREP/Face.js +187 -0
  11. package/src/BREP/MeshRepairer.js +634 -0
  12. package/src/BREP/OffsetShellSolid.js +614 -0
  13. package/src/BREP/PointCloudWrap.js +302 -0
  14. package/src/BREP/Revolve.js +345 -0
  15. package/src/BREP/SolidMethods/authoring.js +112 -0
  16. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  17. package/src/BREP/SolidMethods/chamfer.js +122 -0
  18. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  19. package/src/BREP/SolidMethods/fillet.js +792 -0
  20. package/src/BREP/SolidMethods/index.js +72 -0
  21. package/src/BREP/SolidMethods/io.js +105 -0
  22. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  23. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  24. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  25. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  26. package/src/BREP/SolidMethods/metadata.js +106 -0
  27. package/src/BREP/SolidMethods/metrics.js +51 -0
  28. package/src/BREP/SolidMethods/transforms.js +361 -0
  29. package/src/BREP/SolidMethods/visualize.js +508 -0
  30. package/src/BREP/SolidShared.js +26 -0
  31. package/src/BREP/Sweep.js +1596 -0
  32. package/src/BREP/Tube.js +857 -0
  33. package/src/BREP/Vertex.js +43 -0
  34. package/src/BREP/applyBooleanOperation.js +704 -0
  35. package/src/BREP/boundsUtils.js +48 -0
  36. package/src/BREP/chamfer.js +551 -0
  37. package/src/BREP/edgePolylineUtils.js +85 -0
  38. package/src/BREP/fillets/common.js +388 -0
  39. package/src/BREP/fillets/fillet.js +1422 -0
  40. package/src/BREP/fillets/filletGeometry.js +15 -0
  41. package/src/BREP/fillets/inset.js +389 -0
  42. package/src/BREP/fillets/offsetHelper.js +143 -0
  43. package/src/BREP/fillets/outset.js +88 -0
  44. package/src/BREP/helix.js +193 -0
  45. package/src/BREP/meshToBrep.js +234 -0
  46. package/src/BREP/primitives.js +279 -0
  47. package/src/BREP/setupManifold.js +71 -0
  48. package/src/BREP/threadGeometry.js +1120 -0
  49. package/src/BREP/triangleUtils.js +8 -0
  50. package/src/BREP/triangulate.js +608 -0
  51. package/src/FeatureRegistry.js +183 -0
  52. package/src/PartHistory.js +1132 -0
  53. package/src/UI/AccordionWidget.js +292 -0
  54. package/src/UI/CADmaterials.js +850 -0
  55. package/src/UI/EnvMonacoEditor.js +522 -0
  56. package/src/UI/FloatingWindow.js +396 -0
  57. package/src/UI/HistoryWidget.js +457 -0
  58. package/src/UI/MainToolbar.js +131 -0
  59. package/src/UI/ModelLibraryView.js +194 -0
  60. package/src/UI/OrthoCameraIdle.js +206 -0
  61. package/src/UI/PluginsWidget.js +280 -0
  62. package/src/UI/SceneListing.js +606 -0
  63. package/src/UI/SelectionFilter.js +629 -0
  64. package/src/UI/ViewCube.js +389 -0
  65. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  66. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  67. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  68. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  69. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  70. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  71. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  72. package/src/UI/assembly/constraintPointUtils.js +64 -0
  73. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  74. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  75. package/src/UI/componentSelectorModal.js +240 -0
  76. package/src/UI/controls/CombinedTransformControls.js +386 -0
  77. package/src/UI/dialogs.js +351 -0
  78. package/src/UI/expressionsManager.js +100 -0
  79. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  80. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  81. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  82. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  83. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  84. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  85. package/src/UI/featureDialogWidgets/index.js +34 -0
  86. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  87. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  88. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  89. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  90. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  91. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  92. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  93. package/src/UI/featureDialogWidgets/utils.js +43 -0
  94. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  95. package/src/UI/featureDialogs.js +1414 -0
  96. package/src/UI/fileManagerWidget.js +615 -0
  97. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  98. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  99. package/src/UI/history/historyDisplayInfo.js +133 -0
  100. package/src/UI/mobile.js +28 -0
  101. package/src/UI/objectDump.js +442 -0
  102. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  103. package/src/UI/pmi/AnnotationHistory.js +353 -0
  104. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  105. package/src/UI/pmi/BaseAnnotation.js +269 -0
  106. package/src/UI/pmi/LabelOverlay.css +102 -0
  107. package/src/UI/pmi/LabelOverlay.js +191 -0
  108. package/src/UI/pmi/PMIMode.js +1550 -0
  109. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  110. package/src/UI/pmi/annUtils.js +729 -0
  111. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  112. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  113. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  114. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  115. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  116. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  117. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  118. package/src/UI/pmi/pmiStyle.js +44 -0
  119. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  120. package/src/UI/sketcher/dimensions.js +674 -0
  121. package/src/UI/sketcher/glyphs.js +236 -0
  122. package/src/UI/sketcher/highlights.js +60 -0
  123. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  124. package/src/UI/toolbarButtons/exportButton.js +609 -0
  125. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  126. package/src/UI/toolbarButtons/importButton.js +160 -0
  127. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  128. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  129. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  130. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  131. package/src/UI/toolbarButtons/saveButton.js +99 -0
  132. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  133. package/src/UI/toolbarButtons/testsButton.js +26 -0
  134. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  135. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  136. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  137. package/src/UI/triangleDebuggerWindow.js +945 -0
  138. package/src/UI/viewer.js +4228 -0
  139. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  140. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  141. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  142. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  143. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  144. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  145. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  146. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  147. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  148. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  149. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  150. package/src/core/entities/HistoryCollectionBase.js +72 -0
  151. package/src/core/entities/ListEntityBase.js +109 -0
  152. package/src/core/entities/schemaProcesser.js +121 -0
  153. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  154. package/src/exporters/sheetMetalUnfold.js +862 -0
  155. package/src/exporters/step.js +1135 -0
  156. package/src/exporters/threeMF.js +575 -0
  157. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  158. package/src/features/boolean/BooleanFeature.js +94 -0
  159. package/src/features/chamfer/ChamferFeature.js +116 -0
  160. package/src/features/datium/DatiumFeature.js +80 -0
  161. package/src/features/edgeFeatureUtils.js +41 -0
  162. package/src/features/extrude/ExtrudeFeature.js +143 -0
  163. package/src/features/fillet/FilletFeature.js +197 -0
  164. package/src/features/helix/HelixFeature.js +405 -0
  165. package/src/features/hole/HoleFeature.js +1050 -0
  166. package/src/features/hole/screwClearance.js +86 -0
  167. package/src/features/hole/threadDesignationCatalog.js +149 -0
  168. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  169. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  170. package/src/features/imageToFace/imageEditor.js +1270 -0
  171. package/src/features/imageToFace/traceUtils.js +971 -0
  172. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  173. package/src/features/loft/LoftFeature.js +605 -0
  174. package/src/features/mirror/MirrorFeature.js +151 -0
  175. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  176. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  177. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  178. package/src/features/pattern/PatternFeature.js +275 -0
  179. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  180. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  181. package/src/features/plane/PlaneFeature.js +154 -0
  182. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  183. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  184. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  185. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  186. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  187. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  188. package/src/features/remesh/RemeshFeature.js +97 -0
  189. package/src/features/revolve/RevolveFeature.js +111 -0
  190. package/src/features/selectionUtils.js +118 -0
  191. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  192. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  193. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  194. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  195. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  196. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  197. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  198. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  199. package/src/features/sheetMetal/profileUtils.js +25 -0
  200. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  201. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  202. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  203. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  204. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  205. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  206. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  207. package/src/features/sketch/SketchFeature.js +955 -0
  208. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  209. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  210. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  211. package/src/features/spline/SplineEditorSession.js +988 -0
  212. package/src/features/spline/SplineFeature.js +1388 -0
  213. package/src/features/spline/splineUtils.js +218 -0
  214. package/src/features/sweep/SweepFeature.js +110 -0
  215. package/src/features/transform/TransformFeature.js +152 -0
  216. package/src/features/tube/TubeFeature.js +635 -0
  217. package/src/fs.proxy.js +625 -0
  218. package/src/idbStorage.js +254 -0
  219. package/src/index.js +12 -0
  220. package/src/main.js +15 -0
  221. package/src/metadataManager.js +64 -0
  222. package/src/path.proxy.js +277 -0
  223. package/src/plugins/ghLoader.worker.js +151 -0
  224. package/src/plugins/pluginManager.js +286 -0
  225. package/src/pmi/PMIViewsManager.js +134 -0
  226. package/src/services/componentLibrary.js +198 -0
  227. package/src/tests/ConsoleCapture.js +189 -0
  228. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  229. package/src/tests/browserTests.js +597 -0
  230. package/src/tests/debugBoolean.js +225 -0
  231. package/src/tests/partFiles/badBoolean.json +957 -0
  232. package/src/tests/partFiles/extrudeTest.json +88 -0
  233. package/src/tests/partFiles/filletFail.json +58 -0
  234. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  235. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  236. package/src/tests/test_boolean_subtract.js +27 -0
  237. package/src/tests/test_chamfer.js +17 -0
  238. package/src/tests/test_extrudeFace.js +24 -0
  239. package/src/tests/test_fillet.js +17 -0
  240. package/src/tests/test_fillet_nonClosed.js +45 -0
  241. package/src/tests/test_filletsMoreDifficult.js +46 -0
  242. package/src/tests/test_history_features_basic.js +149 -0
  243. package/src/tests/test_hole.js +282 -0
  244. package/src/tests/test_mirror.js +16 -0
  245. package/src/tests/test_offsetShellGrouping.js +85 -0
  246. package/src/tests/test_plane.js +4 -0
  247. package/src/tests/test_primitiveCone.js +11 -0
  248. package/src/tests/test_primitiveCube.js +7 -0
  249. package/src/tests/test_primitiveCylinder.js +8 -0
  250. package/src/tests/test_primitivePyramid.js +9 -0
  251. package/src/tests/test_primitiveSphere.js +17 -0
  252. package/src/tests/test_primitiveTorus.js +21 -0
  253. package/src/tests/test_pushFace.js +126 -0
  254. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  255. package/src/tests/test_sheetMetal_features.js +80 -0
  256. package/src/tests/test_sketch_openLoop.js +45 -0
  257. package/src/tests/test_solidMetrics.js +58 -0
  258. package/src/tests/test_stlLoader.js +1889 -0
  259. package/src/tests/test_sweepFace.js +55 -0
  260. package/src/tests/test_tube.js +45 -0
  261. package/src/tests/test_tube_closedLoop.js +67 -0
  262. package/src/tests/tests.js +493 -0
  263. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  264. package/src/tools/dialogCapturePageFactory.js +227 -0
  265. package/src/tools/featureDialogCapturePage.js +47 -0
  266. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  267. package/src/utils/axisHelpers.js +99 -0
  268. package/src/utils/deepClone.js +69 -0
  269. package/src/utils/geometryTolerance.js +37 -0
  270. package/src/utils/normalizeTypeString.js +8 -0
  271. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,850 @@
1
+ import * as THREE from 'three';
2
+ import { LineMaterial } from 'three/examples/jsm/Addons.js';
3
+ import { SelectionFilter } from './SelectionFilter.js';
4
+ import { localStorage as LS } from '../idbStorage.js';
5
+
6
+ // CADmaterials for each entity type
7
+
8
+
9
+ export const CADmaterials = {
10
+ PLANE: {
11
+ BASE: new THREE.MeshStandardMaterial({
12
+ color: "#2eff2e",
13
+ side: THREE.DoubleSide,
14
+ transparent: true,
15
+ opacity: .5,
16
+ flatShading: true,
17
+ metalness: 0.05,
18
+ roughness: 0.85,
19
+ depthTest: true,
20
+ depthWrite: true,
21
+ polygonOffset: false,
22
+ emissiveIntensity: 0,
23
+ }),
24
+ SELECTED: new THREE.MeshStandardMaterial({
25
+ color: "#2eff2e",
26
+ side: THREE.DoubleSide,
27
+ transparent: true,
28
+ opacity: .5,
29
+ flatShading: true,
30
+ metalness: 0.05,
31
+ roughness: 0.85,
32
+ depthTest: true,
33
+ depthWrite: false,
34
+ polygonOffset: false,
35
+ emissiveIntensity: 0,
36
+ }),
37
+ },
38
+ EDGE: {
39
+ BASE: new LineMaterial({
40
+ color: "#009dff",
41
+ linewidth: 3,
42
+ transparent: false,
43
+ dashed: true,
44
+ dashSize: 0.5,
45
+ gapSize: 0.5,
46
+ worldUnits: false, // keep dash/line size constant in screen space
47
+ // Depth-test against faces but don't write depth (avoid occluding faces).
48
+ depthWrite: false,
49
+ }),
50
+ SELECTED: new LineMaterial({
51
+ color: "#ff00ff",
52
+ linewidth: 3,
53
+ transparent: false,
54
+ worldUnits: false,
55
+ // Depth-test against faces but don't write depth (avoid occluding faces).
56
+ depthWrite: false,
57
+ }),
58
+ // Overlay variant for helper/centerline edges. Uses depthTest=false so
59
+ // it remains visible through faces. Viewer will keep its resolution
60
+ // updated alongside other fat-line materials.
61
+ // dashed line
62
+ OVERLAY: new LineMaterial({
63
+ color: "#ff0000",
64
+ linewidth: 1.5,
65
+ transparent: true,
66
+ dashed: true,
67
+ dashSize: 0.5,
68
+ gapSize: 0.5,
69
+ worldUnits: false,
70
+ depthTest: false,
71
+ depthWrite: false,
72
+ }),
73
+ // Dashed cyan overlay for symbolic thread major diameter rings
74
+ THREAD_SYMBOLIC_MAJOR: new LineMaterial({
75
+ color: "#00c8ff",
76
+ linewidth: 1.5,
77
+ transparent: true,
78
+ dashed: true,
79
+ dashSize: 0.6,
80
+ gapSize: 0.6,
81
+ worldUnits: false,
82
+ depthTest: false,
83
+ depthWrite: false,
84
+ }),
85
+ },
86
+ LOOP: {
87
+ BASE: new LineMaterial({
88
+ color: "#ff0000",
89
+ linewidth: 1.5,
90
+ transparent: true,
91
+ }),
92
+ SELECTED: new LineMaterial({
93
+ color: "#ff00ff",
94
+ linewidth: 3,
95
+ //linecap: "round",
96
+ //linejoin: "round",
97
+ transparent: true,
98
+ }),
99
+ },
100
+ FACE: {
101
+ BASE: new THREE.MeshStandardMaterial({
102
+ color: "#00009e",
103
+ side: THREE.FrontSide,
104
+ transparent: false,
105
+ opacity: 1,
106
+ flatShading: true,
107
+ metalness: 0.05,
108
+ roughness: 0.85,
109
+ depthTest: true,
110
+ depthWrite: true,
111
+ // Push faces slightly back so coplanar edges can sit on top.
112
+ polygonOffset: true,
113
+ polygonOffsetFactor: 2,
114
+ polygonOffsetUnits: 1,
115
+ emissiveIntensity: 0,
116
+ }),
117
+ SELECTED: new THREE.MeshStandardMaterial({
118
+ color: "#ffc400",
119
+ side: THREE.FrontSide,
120
+ transparent: false,
121
+ opacity: 1,
122
+ wireframe: false,
123
+ flatShading: false,
124
+ metalness: 0,
125
+ roughness: 0.5,
126
+ depthTest: true,
127
+ depthWrite: true,
128
+ // Keep selected faces slightly behind edges as well.
129
+ polygonOffset: true,
130
+ polygonOffsetFactor: 2,
131
+ polygonOffsetUnits: 1,
132
+ emissiveIntensity: 0,
133
+ })
134
+ },
135
+ VERTEX: {
136
+ BASE: new THREE.PointsMaterial({
137
+ color: '#4aff03',
138
+ size: 6,
139
+ sizeAttenuation: false, // keep a consistent pixel size
140
+ transparent: true
141
+ }),
142
+ SELECTED: new THREE.PointsMaterial({
143
+ color: '#00ffff',
144
+ size: 7,
145
+ sizeAttenuation: false,
146
+ transparent: true
147
+ })
148
+ },
149
+ FLAT_PATTERN: {
150
+ OUTER_EDGE: new THREE.LineBasicMaterial({
151
+ color: '#ff5fa2',
152
+ linewidth: 2,
153
+ depthTest: true,
154
+ depthWrite: false,
155
+ }),
156
+ INNER_EDGE: new THREE.LineBasicMaterial({
157
+ color: '#00ffff',
158
+ linewidth: 2,
159
+ depthTest: true,
160
+ depthWrite: false,
161
+ }),
162
+ CENTERLINE: new THREE.LineBasicMaterial({
163
+ color: '#00ffff',
164
+ linewidth: 2,
165
+ depthTest: true,
166
+ depthWrite: false,
167
+ }),
168
+ },
169
+
170
+ };
171
+
172
+
173
+ // this will provide a UI widget to control CAD materials and will allow the user to change the following properties.
174
+ // - Color (html color picker)
175
+ // - Opacity (range slider)
176
+ // - Linewidth (range slider) (only shows on LineBasicMaterial)
177
+ // - Wireframe (checkbox) (only shows on MeshBasicMaterial) items
178
+ //
179
+ // We will make the UI controls for each material in the global CADmaterials object
180
+ export class CADmaterialWidget {
181
+ constructor(viewer = null) {
182
+ this.viewer = viewer || null;
183
+ this.uiElement = document.createElement("div");
184
+ this.uiElement.classList.add('cmw');
185
+ this._storageKey = '__CAD_MATERIAL_SETTINGS__';
186
+ this._settings = this._loadAllSettings();
187
+ this._defaultHoverColor = this._getDefaultHoverColor();
188
+ this._defaultSidebarWidth = this._getDefaultSidebarWidth();
189
+ this._materialEntries = this._collectMaterialEntries();
190
+ this._materialMap = new Map(this._materialEntries.map((entry) => [entry.label, entry.material]));
191
+ this._materialDefaults = this._captureMaterialDefaults(this._materialEntries);
192
+ this._controlRefs = new Map();
193
+ this._ensureStyles();
194
+ this.createUI();
195
+ }
196
+
197
+ createUI() {
198
+ // Hover color control (single global color)
199
+ try {
200
+ const savedHover = this._settings['__HOVER_COLOR__'];
201
+ if (savedHover) SelectionFilter.setHoverColor(savedHover);
202
+ } catch (_) { }
203
+
204
+ const hoverRow = makeRightSpan();
205
+ const hoverLabel = document.createElement('label');
206
+ hoverLabel.className = 'cmw-label';
207
+ hoverLabel.textContent = 'Hover Color';
208
+ hoverRow.appendChild(hoverLabel);
209
+ const hoverInput = document.createElement('input');
210
+ hoverInput.type = 'color';
211
+ hoverInput.className = 'cmw-input';
212
+ const currentHover = this._settings['__HOVER_COLOR__'] || SelectionFilter.getHoverColor() || '#ffd54a';
213
+ // Ensure hex format starting with #
214
+ hoverInput.value = typeof currentHover === 'string' && currentHover.startsWith('#') ? currentHover : `#${new THREE.Color(currentHover).getHexString()}`;
215
+ hoverInput.addEventListener('input', (event) => {
216
+ const v = event.target.value;
217
+ SelectionFilter.setHoverColor(v);
218
+ this._settings['__HOVER_COLOR__'] = v;
219
+ this._saveAllSettings();
220
+ });
221
+ hoverRow.appendChild(hoverInput);
222
+ this.uiElement.appendChild(hoverRow);
223
+ this._hoverInput = hoverInput;
224
+
225
+ // Sidebar width control (global persistent setting)
226
+ const widthRow = makeRightSpan();
227
+ const widthLabel = document.createElement('label');
228
+ widthLabel.className = 'cmw-label';
229
+ widthLabel.textContent = 'Sidebar Width';
230
+ widthRow.appendChild(widthLabel);
231
+
232
+ // Determine initial width
233
+ let initialWidth = 500;
234
+ try {
235
+ const savedW = parseInt(this._settings['__SIDEBAR_WIDTH__']);
236
+ if (Number.isFinite(savedW) && savedW > 0) initialWidth = savedW;
237
+ else {
238
+ const sb = document.getElementById('sidebar');
239
+ const cs = sb ? (sb.style.width || getComputedStyle(sb).width) : '';
240
+ const w = parseInt(cs);
241
+ if (Number.isFinite(w) && w > 0) initialWidth = w;
242
+ }
243
+ } catch { console.log("failed to determine initial sidebar width ") }
244
+
245
+ const widthInput = document.createElement('input');
246
+ widthInput.type = 'number';
247
+ widthInput.inputMode = 'numeric';
248
+ widthInput.className = 'cmw-input';
249
+ widthInput.min = 200;
250
+ widthInput.max = 600;
251
+ widthInput.step = 1;
252
+ widthInput.value = String(initialWidth);
253
+ const applySidebarWidth = (px) => {
254
+ try {
255
+ const sb = document.getElementById('sidebar');
256
+ if (sb && Number.isFinite(px) && px > 0) sb.style.width = `${px}px`;
257
+ } catch { /* ignore */ }
258
+ };
259
+ // Apply saved width immediately
260
+ applySidebarWidth(initialWidth);
261
+ const commitWidth = (raw) => {
262
+ let v = parseInt(raw);
263
+ if (!Number.isFinite(v)) return; // ignore incomplete input
264
+ const min = Number(widthInput.min) || 200;
265
+ const max = Number(widthInput.max) || 600;
266
+ if (v < min) v = min; else if (v > max) v = max;
267
+ widthInput.value = String(v);
268
+ this._applySidebarWidth(v);
269
+ this._settings['__SIDEBAR_WIDTH__'] = v;
270
+ this._saveAllSettings();
271
+ };
272
+ widthInput.addEventListener('change', (event) => commitWidth(event.target.value));
273
+ widthRow.appendChild(widthInput);
274
+ this.uiElement.appendChild(widthRow);
275
+ this._widthInput = widthInput;
276
+
277
+ // Renderer mode control (global persistent setting)
278
+ const rendererRow = makeRightSpan();
279
+ const rendererLabel = document.createElement('label');
280
+ rendererLabel.className = 'cmw-label';
281
+ rendererLabel.textContent = 'Renderer';
282
+ rendererRow.appendChild(rendererLabel);
283
+ const rendererSelect = document.createElement('select');
284
+ rendererSelect.className = 'cmw-input';
285
+ const optWebgl = document.createElement('option');
286
+ optWebgl.value = 'webgl';
287
+ optWebgl.textContent = 'WebGL (Canvas)';
288
+ const optSvg = document.createElement('option');
289
+ optSvg.value = 'svg';
290
+ optSvg.textContent = 'SVG';
291
+ rendererSelect.appendChild(optWebgl);
292
+ rendererSelect.appendChild(optSvg);
293
+ const storedMode = String(this._settings['__RENDERER_MODE__'] || '').toLowerCase();
294
+ const initialMode = storedMode === 'svg' ? 'svg' : 'webgl';
295
+ rendererSelect.value = initialMode;
296
+ rendererSelect.addEventListener('change', (event) => {
297
+ const mode = event?.target?.value === 'svg' ? 'svg' : 'webgl';
298
+ this._settings['__RENDERER_MODE__'] = mode;
299
+ this._saveAllSettings();
300
+ try { this.viewer?.setRendererMode?.(mode); } catch { }
301
+ });
302
+ rendererRow.appendChild(rendererSelect);
303
+ this.uiElement.appendChild(rendererRow);
304
+ this._rendererSelect = rendererSelect;
305
+ try { this.viewer?.setRendererMode?.(initialMode); } catch { }
306
+
307
+ const resetRow = makeRightSpan();
308
+ const resetLabel = document.createElement('label');
309
+ resetLabel.className = 'cmw-label';
310
+ resetLabel.textContent = 'Reset';
311
+ resetRow.appendChild(resetLabel);
312
+ const resetButton = document.createElement('button');
313
+ resetButton.type = 'button';
314
+ resetButton.className = 'cmw-button';
315
+ resetButton.textContent = 'Reset to Defaults';
316
+ resetButton.addEventListener('click', () => this._resetToDefaults());
317
+ resetRow.appendChild(resetButton);
318
+ this.uiElement.appendChild(resetRow);
319
+
320
+ // For each top-level group (e.g., EDGE, LOOP, FACE), render variants (e.g., BASE, SELECTED)
321
+ for (const [groupName, groupVal] of Object.entries(CADmaterials)) {
322
+ const groupContainer = document.createElement("div");
323
+ groupContainer.className = 'cmw-group';
324
+
325
+ // Group header
326
+ const groupHeader = document.createElement('div');
327
+ groupHeader.className = 'cmw-header';
328
+ groupHeader.textContent = groupName;
329
+ groupContainer.appendChild(groupHeader);
330
+
331
+ // Back-compat: allow either a direct THREE.Material or an object of variants
332
+ if (this._isMaterial(groupVal)) {
333
+ const matContainer = document.createElement("div");
334
+ matContainer.className = 'cmw-mat';
335
+ this._buildMaterialControls(matContainer, groupName, groupVal);
336
+ groupContainer.appendChild(matContainer);
337
+ } else if (groupVal && typeof groupVal === 'object') {
338
+ for (const [variantName, mat] of Object.entries(groupVal)) {
339
+ if (!this._isMaterial(mat)) continue;
340
+ const matContainer = document.createElement("div");
341
+ matContainer.className = 'cmw-mat';
342
+ this._buildMaterialControls(matContainer, `${groupName} - ${variantName}`, mat);
343
+ groupContainer.appendChild(matContainer);
344
+ }
345
+ }
346
+
347
+ this.uiElement.appendChild(groupContainer);
348
+ }
349
+
350
+ // Normalize label widths via CSS classes
351
+ }
352
+
353
+ // --- Persistence helpers (browser only) ---
354
+ _loadAllSettings() {
355
+ try {
356
+ const raw = LS.getItem(this._storageKey);
357
+ const obj = raw ? JSON.parse(raw) : {};
358
+ return (obj && typeof obj === 'object') ? obj : {};
359
+ } catch { return {}; }
360
+ }
361
+ _saveAllSettings() {
362
+ try {
363
+ LS.setItem(this._storageKey, JSON.stringify(this._settings, null, 2));
364
+ console.log(JSON.stringify(this._settings, null, 2));
365
+ } catch {/* ignore */ }
366
+ }
367
+ _isMaterial(m) {
368
+ return m && (m.isMaterial === true || m instanceof THREE.Material);
369
+ }
370
+ _collectMaterialEntries() {
371
+ const entries = [];
372
+ for (const [groupName, groupVal] of Object.entries(CADmaterials)) {
373
+ if (this._isMaterial(groupVal)) {
374
+ entries.push({ label: groupName, material: groupVal });
375
+ } else if (groupVal && typeof groupVal === 'object') {
376
+ for (const [variantName, mat] of Object.entries(groupVal)) {
377
+ if (!this._isMaterial(mat)) continue;
378
+ entries.push({ label: `${groupName} - ${variantName}`, material: mat });
379
+ }
380
+ }
381
+ }
382
+ return entries;
383
+ }
384
+ _captureMaterialDefaults(entries) {
385
+ const defaults = {};
386
+ for (const entry of entries) {
387
+ defaults[entry.label] = this._extractMaterialSettings(entry.material);
388
+ }
389
+ return defaults;
390
+ }
391
+ _extractMaterialSettings(material) {
392
+ const settings = {};
393
+ if (material?.color && typeof material.color.getHexString === 'function') {
394
+ settings.color = `#${material.color.getHexString()}`;
395
+ }
396
+ if (material instanceof THREE.LineBasicMaterial || material instanceof LineMaterial) {
397
+ if (material.linewidth != null) settings.linewidth = Number(material.linewidth);
398
+ }
399
+ if (material instanceof THREE.PointsMaterial) {
400
+ if (material.size != null) settings.pointSize = Number(material.size);
401
+ }
402
+ if (
403
+ material instanceof THREE.MeshBasicMaterial ||
404
+ material instanceof THREE.MeshMatcapMaterial ||
405
+ material instanceof THREE.MeshToonMaterial ||
406
+ material instanceof THREE.MeshStandardMaterial
407
+ ) {
408
+ if (material.opacity != null) settings.opacity = Number(material.opacity);
409
+ settings.transparent = !!material.transparent;
410
+ settings.wireframe = !!material.wireframe;
411
+ settings.side = material.side;
412
+ }
413
+ return settings;
414
+ }
415
+ _applyMaterialSettings(material, settings) {
416
+ if (!material || !settings) return;
417
+ if (settings.color && material.color && typeof material.color.set === 'function') {
418
+ material.color.set(this._sanitizeHexColor(settings.color));
419
+ }
420
+ if (material instanceof THREE.LineBasicMaterial || material instanceof LineMaterial) {
421
+ if (settings.linewidth != null) material.linewidth = Number(settings.linewidth);
422
+ }
423
+ if (material instanceof THREE.PointsMaterial) {
424
+ if (settings.pointSize != null) material.size = Number(settings.pointSize);
425
+ }
426
+ if (
427
+ material instanceof THREE.MeshBasicMaterial ||
428
+ material instanceof THREE.MeshMatcapMaterial ||
429
+ material instanceof THREE.MeshToonMaterial ||
430
+ material instanceof THREE.MeshStandardMaterial
431
+ ) {
432
+ if (settings.opacity != null) material.opacity = Number(settings.opacity);
433
+ if (settings.transparent != null) material.transparent = !!settings.transparent;
434
+ if (settings.wireframe != null) material.wireframe = !!settings.wireframe;
435
+ if (settings.side != null) material.side = settings.side;
436
+ }
437
+ }
438
+ _applySidebarWidth(px) {
439
+ try {
440
+ const sb = document.getElementById('sidebar');
441
+ if (sb && Number.isFinite(px) && px > 0) sb.style.width = `${px}px`;
442
+ } catch { /* ignore */ }
443
+ }
444
+ setSidebarWidth(px, { persist = true } = {}) {
445
+ let v = Number(px);
446
+ if (!Number.isFinite(v)) return null;
447
+ const min = Number(this._widthInput?.min) || 200;
448
+ const max = Number(this._widthInput?.max) || 600;
449
+ if (v < min) v = min; else if (v > max) v = max;
450
+ this._setSidebarWidthUi(v);
451
+ this._settings['__SIDEBAR_WIDTH__'] = v;
452
+ if (persist) this._saveAllSettings();
453
+ return v;
454
+ }
455
+ _setSidebarWidthUi(px) {
456
+ if (!this._widthInput) {
457
+ this._applySidebarWidth(px);
458
+ return;
459
+ }
460
+ let v = Number(px);
461
+ if (!Number.isFinite(v)) return;
462
+ const min = Number(this._widthInput.min) || 200;
463
+ const max = Number(this._widthInput.max) || 600;
464
+ if (v < min) v = min; else if (v > max) v = max;
465
+ this._widthInput.value = String(v);
466
+ this._applySidebarWidth(v);
467
+ }
468
+ _normalizeHexColor(value) {
469
+ if (typeof value === 'string' && value.startsWith('#')) return this._sanitizeHexColor(value);
470
+ try { return `#${new THREE.Color(value).getHexString()}`; } catch { return '#ffd54a'; }
471
+ }
472
+ _getDefaultHoverColor() {
473
+ return this._normalizeHexColor(SelectionFilter.getHoverColor() || '#ffd54a');
474
+ }
475
+ _getDefaultSidebarWidth() {
476
+ const fallback = 500;
477
+ try {
478
+ const sb = document.getElementById('sidebar');
479
+ if (!sb) return fallback;
480
+ const prev = sb.style.width;
481
+ if (prev) sb.style.width = '';
482
+ const cs = getComputedStyle(sb).width;
483
+ if (prev) sb.style.width = prev;
484
+ const w = parseInt(cs);
485
+ if (Number.isFinite(w) && w > 0) return w;
486
+ } catch { /* keep fallback */ }
487
+ return fallback;
488
+ }
489
+ _formatRangeValue(value, step) {
490
+ const v = Number(value);
491
+ if (!Number.isFinite(v)) return '';
492
+ const stepStr = step != null ? String(step) : '';
493
+ let decimals = 0;
494
+ if (stepStr.includes('.')) decimals = stepStr.split('.')[1].length;
495
+ if (decimals > 0) {
496
+ const fixed = v.toFixed(decimals);
497
+ return fixed.replace(/\.?0+$/, '');
498
+ }
499
+ return String(Math.round(v));
500
+ }
501
+ _getRangeThumbSize(input) {
502
+ if (!input) return 16;
503
+ const cached = Number(input.dataset.cmwThumb);
504
+ if (Number.isFinite(cached) && cached > 0) return cached;
505
+ let measured = input.offsetHeight || 0;
506
+ if (!measured) {
507
+ try {
508
+ measured = parseFloat(getComputedStyle(input).height) || 0;
509
+ } catch { /* ignore */ }
510
+ }
511
+ const size = measured > 0 ? measured : 16;
512
+ if (measured > 0) input.dataset.cmwThumb = String(size);
513
+ return size;
514
+ }
515
+ _updateRangeBubble(input, bubble) {
516
+ if (!input || !bubble) return;
517
+ const min = Number(input.min || 0);
518
+ const max = Number(input.max || 100);
519
+ const value = Number(input.value);
520
+ if (!Number.isFinite(value) || !Number.isFinite(min) || !Number.isFinite(max) || max <= min) {
521
+ bubble.textContent = '';
522
+ bubble.style.left = '0%';
523
+ bubble.style.transform = 'translateX(-50%)';
524
+ return;
525
+ }
526
+ const pct = Math.min(1, Math.max(0, (value - min) / (max - min)));
527
+ const thumbSize = this._getRangeThumbSize(input);
528
+ const offset = (thumbSize / 2) - (pct * thumbSize);
529
+ bubble.textContent = this._formatRangeValue(value, input.step);
530
+ bubble.style.left = `${pct * 100}%`;
531
+ bubble.style.transform = `translateX(-50%) translateX(${offset}px)`;
532
+ }
533
+ _createRangeField(input) {
534
+ const wrap = document.createElement('div');
535
+ wrap.className = 'cmw-range-wrap';
536
+ wrap.appendChild(input);
537
+ const bubble = document.createElement('span');
538
+ bubble.className = 'cmw-range-bubble';
539
+ wrap.appendChild(bubble);
540
+ this._updateRangeBubble(input, bubble);
541
+ return { wrap, bubble };
542
+ }
543
+ _createRangeRow({ label, min, max, step, value, onInput }) {
544
+ const row = makeRightSpan();
545
+ row.classList.add('cmw-row-range');
546
+ const labelEl = document.createElement('label');
547
+ labelEl.className = 'cmw-label';
548
+ labelEl.textContent = label;
549
+ row.appendChild(labelEl);
550
+ const input = document.createElement('input');
551
+ input.type = 'range';
552
+ input.className = 'cmw-range';
553
+ input.min = min;
554
+ input.max = max;
555
+ input.step = step;
556
+ input.value = Number.isFinite(value) ? value : min;
557
+ const { wrap, bubble } = this._createRangeField(input);
558
+ input.addEventListener('input', (event) => {
559
+ const v = parseFloat(event.target.value);
560
+ if (Number.isFinite(v)) onInput(v);
561
+ this._updateRangeBubble(input, bubble);
562
+ });
563
+ row.appendChild(wrap);
564
+ return { row, input, bubble };
565
+ }
566
+ _syncMaterialControls(labelText, material) {
567
+ const controls = this._controlRefs.get(labelText);
568
+ if (!controls || !material) return;
569
+ if (controls.colorInput && material.color && typeof material.color.getHexString === 'function') {
570
+ controls.colorInput.value = `#${material.color.getHexString()}`;
571
+ }
572
+ if (controls.lineWidthInput) {
573
+ if (material.linewidth != null) controls.lineWidthInput.value = material.linewidth;
574
+ if (controls.lineWidthBubble) this._updateRangeBubble(controls.lineWidthInput, controls.lineWidthBubble);
575
+ }
576
+ if (controls.pointSizeInput) {
577
+ if (material.size != null) controls.pointSizeInput.value = material.size;
578
+ if (controls.pointSizeBubble) this._updateRangeBubble(controls.pointSizeInput, controls.pointSizeBubble);
579
+ }
580
+ if (controls.opacityInput) {
581
+ controls.opacityInput.value = material.opacity ?? 1;
582
+ if (controls.opacityBubble) this._updateRangeBubble(controls.opacityInput, controls.opacityBubble);
583
+ }
584
+ if (controls.wireframeInput) {
585
+ controls.wireframeInput.checked = !!material.wireframe;
586
+ }
587
+ if (controls.doubleSidedInput) {
588
+ controls.doubleSidedInput.checked = material.side === THREE.DoubleSide;
589
+ }
590
+ }
591
+ _resetToDefaults() {
592
+ this._settings = {};
593
+ try { LS.removeItem(this._storageKey); } catch { /* ignore */ }
594
+
595
+ const hoverColor = this._normalizeHexColor(this._defaultHoverColor);
596
+ SelectionFilter.setHoverColor(hoverColor);
597
+ if (this._hoverInput) this._hoverInput.value = hoverColor;
598
+
599
+ this._setSidebarWidthUi(this._defaultSidebarWidth);
600
+
601
+ for (const [labelText, defaults] of Object.entries(this._materialDefaults || {})) {
602
+ const material = this._materialMap.get(labelText);
603
+ if (!material) continue;
604
+ this._applyMaterialSettings(material, defaults);
605
+ this._syncMaterialControls(labelText, material);
606
+ }
607
+ }
608
+ _getMatKey(labelText) {
609
+ return String(labelText);
610
+ }
611
+ _getSettingsFor(labelText) {
612
+ const key = this._getMatKey(labelText);
613
+ return this._settings[key] || {};
614
+ }
615
+ _setSettingsFor(labelText, kv) {
616
+ const key = this._getMatKey(labelText);
617
+ const prev = this._settings[key] || {};
618
+ this._settings[key] = { ...prev, ...kv };
619
+ this._saveAllSettings();
620
+ }
621
+
622
+ _sanitizeHexColor(value) {
623
+ if (typeof value !== 'string') return value;
624
+ if (!value.startsWith('#')) return value;
625
+ // If color is in #RRGGBBAA form, drop alpha AA
626
+ if (value.length === 9) return value.slice(0, 7);
627
+ return value;
628
+ }
629
+
630
+ _applySavedToMaterial(labelText, material) {
631
+ const s = this._getSettingsFor(labelText);
632
+ if (s.color && material.color && typeof material.color.set === 'function') {
633
+ material.color.set(this._sanitizeHexColor(s.color));
634
+ }
635
+ if (material instanceof THREE.LineBasicMaterial || material instanceof LineMaterial) {
636
+ if (s.linewidth != null) material.linewidth = Number(s.linewidth);
637
+ }
638
+ if (material instanceof THREE.PointsMaterial) {
639
+ if (s.pointSize != null) material.size = Number(s.pointSize);
640
+ }
641
+ if (
642
+ material instanceof THREE.MeshBasicMaterial ||
643
+ material instanceof THREE.MeshMatcapMaterial ||
644
+ material instanceof THREE.MeshToonMaterial ||
645
+ material instanceof THREE.MeshStandardMaterial
646
+ ) {
647
+ if (s.opacity != null) {
648
+ material.opacity = Number(s.opacity);
649
+ material.transparent = material.opacity < 1;
650
+ }
651
+ if (s.wireframe != null) material.wireframe = !!s.wireframe;
652
+ if (s.doubleSided != null) material.side = s.doubleSided ? THREE.DoubleSide : THREE.FrontSide;
653
+ }
654
+ }
655
+
656
+ _buildMaterialControls(container, labelText, material) {
657
+ // Apply saved settings first
658
+ this._applySavedToMaterial(labelText, material);
659
+ const controls = this._controlRefs.get(labelText) || {};
660
+
661
+ // Color row
662
+ if (material.color && typeof material.color.getHexString === 'function') {
663
+ const colorRow = makeRightSpan();
664
+ const colorLabel = document.createElement("label");
665
+ colorLabel.className = 'cmw-label';
666
+ colorLabel.textContent = labelText;
667
+ colorRow.appendChild(colorLabel);
668
+ const colorInput = document.createElement("input");
669
+ colorInput.type = "color";
670
+ colorInput.className = 'cmw-input';
671
+ colorInput.value = `#${material.color.getHexString()}`;
672
+ colorInput.addEventListener("input", (event) => {
673
+ const v = this._sanitizeHexColor(event.target.value);
674
+ // Normalize UI value back to sanitized form so user sees what is applied
675
+ if (v !== event.target.value) event.target.value = v;
676
+ material.color.set(v);
677
+ this._setSettingsFor(labelText, { color: v });
678
+ });
679
+ colorRow.appendChild(colorInput);
680
+ container.appendChild(colorRow);
681
+ controls.colorInput = colorInput;
682
+ }
683
+
684
+ // Line-specific controls
685
+ if (material instanceof THREE.LineBasicMaterial || material instanceof LineMaterial) {
686
+ const { row, input, bubble } = this._createRangeRow({
687
+ label: 'Linewidth',
688
+ min: 1,
689
+ max: 10,
690
+ step: 0.1,
691
+ value: material.linewidth ?? 1,
692
+ onInput: (v) => {
693
+ material.linewidth = v;
694
+ this._setSettingsFor(labelText, { linewidth: v });
695
+ },
696
+ });
697
+ container.appendChild(row);
698
+ controls.lineWidthInput = input;
699
+ controls.lineWidthBubble = bubble;
700
+ }
701
+
702
+ // Points-specific controls
703
+ if (material instanceof THREE.PointsMaterial) {
704
+ const { row, input, bubble } = this._createRangeRow({
705
+ label: 'Point Size',
706
+ min: 1,
707
+ max: 30,
708
+ step: 0.5,
709
+ value: material.size ?? 6,
710
+ onInput: (v) => {
711
+ material.size = v;
712
+ this._setSettingsFor(labelText, { pointSize: v });
713
+ },
714
+ });
715
+ container.appendChild(row);
716
+ controls.pointSizeInput = input;
717
+ controls.pointSizeBubble = bubble;
718
+ }
719
+
720
+ // Mesh material common controls
721
+ if (
722
+ material instanceof THREE.MeshBasicMaterial ||
723
+ material instanceof THREE.MeshMatcapMaterial ||
724
+ material instanceof THREE.MeshToonMaterial ||
725
+ material instanceof THREE.MeshStandardMaterial
726
+ ) {
727
+ // Opacity
728
+ const { row, input, bubble } = this._createRangeRow({
729
+ label: 'Opacity',
730
+ min: 0,
731
+ max: 1,
732
+ step: 0.01,
733
+ value: material.opacity ?? 1,
734
+ onInput: (v) => {
735
+ material.opacity = v;
736
+ material.transparent = material.opacity < 1;
737
+ this._setSettingsFor(labelText, { opacity: material.opacity });
738
+ },
739
+ });
740
+ container.appendChild(row);
741
+ controls.opacityInput = input;
742
+ controls.opacityBubble = bubble;
743
+
744
+ // Wireframe
745
+ const wfRow = makeRightSpan();
746
+ const wfLabel = document.createElement("label");
747
+ wfLabel.className = 'cmw-label';
748
+ wfLabel.textContent = "Wireframe";
749
+ wfRow.appendChild(wfLabel);
750
+ const wfInput = document.createElement("input");
751
+ wfInput.type = "checkbox";
752
+ wfInput.className = 'cmw-check';
753
+ wfInput.checked = !!material.wireframe;
754
+ wfInput.addEventListener("change", (event) => {
755
+ material.wireframe = !!event.target.checked;
756
+ this._setSettingsFor(labelText, { wireframe: material.wireframe });
757
+ });
758
+ wfRow.appendChild(wfInput);
759
+ container.appendChild(wfRow);
760
+ controls.wireframeInput = wfInput;
761
+
762
+ // Double sided
763
+ const dsRow = makeRightSpan();
764
+ const dsLabel = document.createElement("label");
765
+ dsLabel.className = 'cmw-label';
766
+ dsLabel.textContent = "Double Sided";
767
+ dsRow.appendChild(dsLabel);
768
+ const dsInput = document.createElement("input");
769
+ dsInput.type = "checkbox";
770
+ dsInput.className = 'cmw-check';
771
+ dsInput.checked = material.side === THREE.DoubleSide;
772
+ dsInput.addEventListener("change", (event) => {
773
+ material.side = event.target.checked ? THREE.DoubleSide : THREE.FrontSide;
774
+ this._setSettingsFor(labelText, { doubleSided: event.target.checked });
775
+ });
776
+ dsRow.appendChild(dsInput);
777
+ container.appendChild(dsRow);
778
+ controls.doubleSidedInput = dsInput;
779
+ }
780
+
781
+ this._controlRefs.set(labelText, controls);
782
+ }
783
+
784
+ _ensureStyles() {
785
+ if (document.getElementById('cad-materials-widget-styles')) return;
786
+ const style = document.createElement('style');
787
+ style.id = 'cad-materials-widget-styles';
788
+ style.textContent = `
789
+ /* Use HistoryWidget vars when present; fallback to similar values */
790
+ :root { --cmw-border: var(--border, #262b36); --cmw-text: var(--text, #e6e6e6); --cmw-bg: var(--bg-elev, #12141b); }
791
+ .cmw { display: flex; flex-direction: column; gap: 8px; color: var(--cmw-text); }
792
+ .cmw-group {
793
+ background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(255,255,255,.01));
794
+ border: 1px solid var(--cmw-border);
795
+ border-radius: 10px;
796
+ overflow: hidden;
797
+ }
798
+ .cmw-header {
799
+ padding: 10px 12px;
800
+ font-weight: 700;
801
+ color: var(--cmw-text);
802
+ border-bottom: 1px solid var(--cmw-border);
803
+ background: transparent;
804
+ }
805
+ .cmw-mat { display: flex; flex-direction: column; }
806
+ .cmw-row { display: flex; align-items: center; gap: 10px; padding: 8px 12px; }
807
+ .cmw-row-range { align-items: flex-start; }
808
+ .cmw-row-range .cmw-label { margin-top: 20px; }
809
+ .cmw-label { width: 160px; color: var(--cmw-text); }
810
+ .cmw-input { background: #0b0e14; color: var(--cmw-text); border: 1px solid #374151; border-radius: 8px; padding: 4px 6px; height: 28px; }
811
+ .cmw-range-wrap { position: relative; width: 200px; padding-top: 20px; }
812
+ .cmw-range { width: 100%; accent-color: #60a5fa; }
813
+ .cmw-range-bubble {
814
+ position: absolute;
815
+ top: 0;
816
+ left: 0;
817
+ transform: translateX(-50%);
818
+ background: #0b0e14;
819
+ border: 1px solid #374151;
820
+ border-radius: 6px;
821
+ padding: 2px 6px;
822
+ font-size: 12px;
823
+ line-height: 1.2;
824
+ color: #d1d5db;
825
+ pointer-events: none;
826
+ white-space: nowrap;
827
+ }
828
+ .cmw-check { accent-color: #60a5fa; }
829
+ .cmw-button {
830
+ background: #111827;
831
+ color: var(--cmw-text);
832
+ border: 1px solid #374151;
833
+ border-radius: 8px;
834
+ padding: 6px 10px;
835
+ cursor: pointer;
836
+ }
837
+ .cmw-button:hover { border-color: #60a5fa; }
838
+ `;
839
+ document.head.appendChild(style);
840
+ }
841
+ }
842
+
843
+
844
+
845
+
846
+ function makeRightSpan() {
847
+ const row = document.createElement('div');
848
+ row.className = 'cmw-row';
849
+ return row;
850
+ }