brep-io-kernel 1.0.0-ci.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (271) hide show
  1. package/LICENSE.md +32 -0
  2. package/README.md +157 -0
  3. package/dist-kernel/brep-kernel.js +74699 -0
  4. package/package.json +58 -0
  5. package/src/BREP/AssemblyComponent.js +42 -0
  6. package/src/BREP/BREP.js +43 -0
  7. package/src/BREP/BetterSolid.js +805 -0
  8. package/src/BREP/Edge.js +103 -0
  9. package/src/BREP/Extrude.js +403 -0
  10. package/src/BREP/Face.js +187 -0
  11. package/src/BREP/MeshRepairer.js +634 -0
  12. package/src/BREP/OffsetShellSolid.js +614 -0
  13. package/src/BREP/PointCloudWrap.js +302 -0
  14. package/src/BREP/Revolve.js +345 -0
  15. package/src/BREP/SolidMethods/authoring.js +112 -0
  16. package/src/BREP/SolidMethods/booleanOps.js +230 -0
  17. package/src/BREP/SolidMethods/chamfer.js +122 -0
  18. package/src/BREP/SolidMethods/edgeResolution.js +25 -0
  19. package/src/BREP/SolidMethods/fillet.js +792 -0
  20. package/src/BREP/SolidMethods/index.js +72 -0
  21. package/src/BREP/SolidMethods/io.js +105 -0
  22. package/src/BREP/SolidMethods/lifecycle.js +103 -0
  23. package/src/BREP/SolidMethods/manifoldOps.js +375 -0
  24. package/src/BREP/SolidMethods/meshCleanup.js +2512 -0
  25. package/src/BREP/SolidMethods/meshQueries.js +264 -0
  26. package/src/BREP/SolidMethods/metadata.js +106 -0
  27. package/src/BREP/SolidMethods/metrics.js +51 -0
  28. package/src/BREP/SolidMethods/transforms.js +361 -0
  29. package/src/BREP/SolidMethods/visualize.js +508 -0
  30. package/src/BREP/SolidShared.js +26 -0
  31. package/src/BREP/Sweep.js +1596 -0
  32. package/src/BREP/Tube.js +857 -0
  33. package/src/BREP/Vertex.js +43 -0
  34. package/src/BREP/applyBooleanOperation.js +704 -0
  35. package/src/BREP/boundsUtils.js +48 -0
  36. package/src/BREP/chamfer.js +551 -0
  37. package/src/BREP/edgePolylineUtils.js +85 -0
  38. package/src/BREP/fillets/common.js +388 -0
  39. package/src/BREP/fillets/fillet.js +1422 -0
  40. package/src/BREP/fillets/filletGeometry.js +15 -0
  41. package/src/BREP/fillets/inset.js +389 -0
  42. package/src/BREP/fillets/offsetHelper.js +143 -0
  43. package/src/BREP/fillets/outset.js +88 -0
  44. package/src/BREP/helix.js +193 -0
  45. package/src/BREP/meshToBrep.js +234 -0
  46. package/src/BREP/primitives.js +279 -0
  47. package/src/BREP/setupManifold.js +71 -0
  48. package/src/BREP/threadGeometry.js +1120 -0
  49. package/src/BREP/triangleUtils.js +8 -0
  50. package/src/BREP/triangulate.js +608 -0
  51. package/src/FeatureRegistry.js +183 -0
  52. package/src/PartHistory.js +1132 -0
  53. package/src/UI/AccordionWidget.js +292 -0
  54. package/src/UI/CADmaterials.js +850 -0
  55. package/src/UI/EnvMonacoEditor.js +522 -0
  56. package/src/UI/FloatingWindow.js +396 -0
  57. package/src/UI/HistoryWidget.js +457 -0
  58. package/src/UI/MainToolbar.js +131 -0
  59. package/src/UI/ModelLibraryView.js +194 -0
  60. package/src/UI/OrthoCameraIdle.js +206 -0
  61. package/src/UI/PluginsWidget.js +280 -0
  62. package/src/UI/SceneListing.js +606 -0
  63. package/src/UI/SelectionFilter.js +629 -0
  64. package/src/UI/ViewCube.js +389 -0
  65. package/src/UI/assembly/AssemblyConstraintCollectionWidget.js +329 -0
  66. package/src/UI/assembly/AssemblyConstraintControlsWidget.js +282 -0
  67. package/src/UI/assembly/AssemblyConstraintsWidget.css +292 -0
  68. package/src/UI/assembly/AssemblyConstraintsWidget.js +1373 -0
  69. package/src/UI/assembly/constraintFaceUtils.js +115 -0
  70. package/src/UI/assembly/constraintHighlightUtils.js +70 -0
  71. package/src/UI/assembly/constraintLabelUtils.js +31 -0
  72. package/src/UI/assembly/constraintPointUtils.js +64 -0
  73. package/src/UI/assembly/constraintSelectionUtils.js +185 -0
  74. package/src/UI/assembly/constraintStatusUtils.js +142 -0
  75. package/src/UI/componentSelectorModal.js +240 -0
  76. package/src/UI/controls/CombinedTransformControls.js +386 -0
  77. package/src/UI/dialogs.js +351 -0
  78. package/src/UI/expressionsManager.js +100 -0
  79. package/src/UI/featureDialogWidgets/booleanField.js +25 -0
  80. package/src/UI/featureDialogWidgets/booleanOperationField.js +97 -0
  81. package/src/UI/featureDialogWidgets/buttonField.js +45 -0
  82. package/src/UI/featureDialogWidgets/componentSelectorField.js +102 -0
  83. package/src/UI/featureDialogWidgets/defaultField.js +23 -0
  84. package/src/UI/featureDialogWidgets/fileField.js +66 -0
  85. package/src/UI/featureDialogWidgets/index.js +34 -0
  86. package/src/UI/featureDialogWidgets/numberField.js +165 -0
  87. package/src/UI/featureDialogWidgets/optionsField.js +33 -0
  88. package/src/UI/featureDialogWidgets/referenceSelectionField.js +208 -0
  89. package/src/UI/featureDialogWidgets/stringField.js +24 -0
  90. package/src/UI/featureDialogWidgets/textareaField.js +28 -0
  91. package/src/UI/featureDialogWidgets/threadDesignationField.js +160 -0
  92. package/src/UI/featureDialogWidgets/transformField.js +252 -0
  93. package/src/UI/featureDialogWidgets/utils.js +43 -0
  94. package/src/UI/featureDialogWidgets/vec3Field.js +133 -0
  95. package/src/UI/featureDialogs.js +1414 -0
  96. package/src/UI/fileManagerWidget.js +615 -0
  97. package/src/UI/history/HistoryCollectionWidget.js +1294 -0
  98. package/src/UI/history/historyCollectionWidget.css.js +257 -0
  99. package/src/UI/history/historyDisplayInfo.js +133 -0
  100. package/src/UI/mobile.js +28 -0
  101. package/src/UI/objectDump.js +442 -0
  102. package/src/UI/pmi/AnnotationCollectionWidget.js +120 -0
  103. package/src/UI/pmi/AnnotationHistory.js +353 -0
  104. package/src/UI/pmi/AnnotationRegistry.js +90 -0
  105. package/src/UI/pmi/BaseAnnotation.js +269 -0
  106. package/src/UI/pmi/LabelOverlay.css +102 -0
  107. package/src/UI/pmi/LabelOverlay.js +191 -0
  108. package/src/UI/pmi/PMIMode.js +1550 -0
  109. package/src/UI/pmi/PMIViewsWidget.js +1098 -0
  110. package/src/UI/pmi/annUtils.js +729 -0
  111. package/src/UI/pmi/dimensions/AngleDimensionAnnotation.js +647 -0
  112. package/src/UI/pmi/dimensions/ExplodeBodyAnnotation.js +507 -0
  113. package/src/UI/pmi/dimensions/HoleCalloutAnnotation.js +462 -0
  114. package/src/UI/pmi/dimensions/LeaderAnnotation.js +403 -0
  115. package/src/UI/pmi/dimensions/LinearDimensionAnnotation.js +532 -0
  116. package/src/UI/pmi/dimensions/NoteAnnotation.js +110 -0
  117. package/src/UI/pmi/dimensions/RadialDimensionAnnotation.js +659 -0
  118. package/src/UI/pmi/pmiStyle.js +44 -0
  119. package/src/UI/sketcher/SketchMode3D.js +4095 -0
  120. package/src/UI/sketcher/dimensions.js +674 -0
  121. package/src/UI/sketcher/glyphs.js +236 -0
  122. package/src/UI/sketcher/highlights.js +60 -0
  123. package/src/UI/toolbarButtons/aboutButton.js +5 -0
  124. package/src/UI/toolbarButtons/exportButton.js +609 -0
  125. package/src/UI/toolbarButtons/flatPatternButton.js +307 -0
  126. package/src/UI/toolbarButtons/importButton.js +160 -0
  127. package/src/UI/toolbarButtons/inspectorToggleButton.js +12 -0
  128. package/src/UI/toolbarButtons/metadataButton.js +1063 -0
  129. package/src/UI/toolbarButtons/orientToFaceButton.js +114 -0
  130. package/src/UI/toolbarButtons/registerDefaultButtons.js +46 -0
  131. package/src/UI/toolbarButtons/saveButton.js +99 -0
  132. package/src/UI/toolbarButtons/scriptRunnerButton.js +302 -0
  133. package/src/UI/toolbarButtons/testsButton.js +26 -0
  134. package/src/UI/toolbarButtons/undoRedoButtons.js +25 -0
  135. package/src/UI/toolbarButtons/wireframeToggleButton.js +5 -0
  136. package/src/UI/toolbarButtons/zoomToFitButton.js +5 -0
  137. package/src/UI/triangleDebuggerWindow.js +945 -0
  138. package/src/UI/viewer.js +4228 -0
  139. package/src/assemblyConstraints/AssemblyConstraintHistory.js +1576 -0
  140. package/src/assemblyConstraints/AssemblyConstraintRegistry.js +120 -0
  141. package/src/assemblyConstraints/BaseAssemblyConstraint.js +66 -0
  142. package/src/assemblyConstraints/constraintExpressionUtils.js +35 -0
  143. package/src/assemblyConstraints/constraintUtils/parallelAlignment.js +676 -0
  144. package/src/assemblyConstraints/constraints/AngleConstraint.js +485 -0
  145. package/src/assemblyConstraints/constraints/CoincidentConstraint.js +194 -0
  146. package/src/assemblyConstraints/constraints/DistanceConstraint.js +616 -0
  147. package/src/assemblyConstraints/constraints/FixedConstraint.js +78 -0
  148. package/src/assemblyConstraints/constraints/ParallelConstraint.js +252 -0
  149. package/src/assemblyConstraints/constraints/TouchAlignConstraint.js +961 -0
  150. package/src/core/entities/HistoryCollectionBase.js +72 -0
  151. package/src/core/entities/ListEntityBase.js +109 -0
  152. package/src/core/entities/schemaProcesser.js +121 -0
  153. package/src/exporters/sheetMetalFlatPattern.js +659 -0
  154. package/src/exporters/sheetMetalUnfold.js +862 -0
  155. package/src/exporters/step.js +1135 -0
  156. package/src/exporters/threeMF.js +575 -0
  157. package/src/features/assemblyComponent/AssemblyComponentFeature.js +780 -0
  158. package/src/features/boolean/BooleanFeature.js +94 -0
  159. package/src/features/chamfer/ChamferFeature.js +116 -0
  160. package/src/features/datium/DatiumFeature.js +80 -0
  161. package/src/features/edgeFeatureUtils.js +41 -0
  162. package/src/features/extrude/ExtrudeFeature.js +143 -0
  163. package/src/features/fillet/FilletFeature.js +197 -0
  164. package/src/features/helix/HelixFeature.js +405 -0
  165. package/src/features/hole/HoleFeature.js +1050 -0
  166. package/src/features/hole/screwClearance.js +86 -0
  167. package/src/features/hole/threadDesignationCatalog.js +149 -0
  168. package/src/features/imageHeightSolid/ImageHeightmapSolidFeature.js +463 -0
  169. package/src/features/imageToFace/ImageToFaceFeature.js +727 -0
  170. package/src/features/imageToFace/imageEditor.js +1270 -0
  171. package/src/features/imageToFace/traceUtils.js +971 -0
  172. package/src/features/import3dModel/Import3dModelFeature.js +151 -0
  173. package/src/features/loft/LoftFeature.js +605 -0
  174. package/src/features/mirror/MirrorFeature.js +151 -0
  175. package/src/features/offsetFace/OffsetFaceFeature.js +370 -0
  176. package/src/features/offsetShell/OffsetShellFeature.js +89 -0
  177. package/src/features/overlapCleanup/OverlapCleanupFeature.js +85 -0
  178. package/src/features/pattern/PatternFeature.js +275 -0
  179. package/src/features/patternLinear/PatternLinearFeature.js +120 -0
  180. package/src/features/patternRadial/PatternRadialFeature.js +186 -0
  181. package/src/features/plane/PlaneFeature.js +154 -0
  182. package/src/features/primitiveCone/primitiveConeFeature.js +99 -0
  183. package/src/features/primitiveCube/primitiveCubeFeature.js +70 -0
  184. package/src/features/primitiveCylinder/primitiveCylinderFeature.js +91 -0
  185. package/src/features/primitivePyramid/primitivePyramidFeature.js +72 -0
  186. package/src/features/primitiveSphere/primitiveSphereFeature.js +62 -0
  187. package/src/features/primitiveTorus/primitiveTorusFeature.js +109 -0
  188. package/src/features/remesh/RemeshFeature.js +97 -0
  189. package/src/features/revolve/RevolveFeature.js +111 -0
  190. package/src/features/selectionUtils.js +118 -0
  191. package/src/features/sheetMetal/SheetMetalContourFlangeFeature.js +1656 -0
  192. package/src/features/sheetMetal/SheetMetalCutoutFeature.js +1056 -0
  193. package/src/features/sheetMetal/SheetMetalFlangeFeature.js +1568 -0
  194. package/src/features/sheetMetal/SheetMetalHemFeature.js +43 -0
  195. package/src/features/sheetMetal/SheetMetalObject.js +141 -0
  196. package/src/features/sheetMetal/SheetMetalTabFeature.js +176 -0
  197. package/src/features/sheetMetal/UNFOLD_NEUTRAL_REQUIREMENTS.md +153 -0
  198. package/src/features/sheetMetal/contour-flange-rebuild-spec.md +261 -0
  199. package/src/features/sheetMetal/profileUtils.js +25 -0
  200. package/src/features/sheetMetal/sheetMetalCleanup.js +9 -0
  201. package/src/features/sheetMetal/sheetMetalFaceTypes.js +146 -0
  202. package/src/features/sheetMetal/sheetMetalMetadata.js +165 -0
  203. package/src/features/sheetMetal/sheetMetalPipeline.js +169 -0
  204. package/src/features/sheetMetal/sheetMetalProfileUtils.js +216 -0
  205. package/src/features/sheetMetal/sheetMetalTabUtils.js +29 -0
  206. package/src/features/sheetMetal/sheetMetalTree.js +210 -0
  207. package/src/features/sketch/SketchFeature.js +955 -0
  208. package/src/features/sketch/sketchSolver2D/ConstraintEngine.js +800 -0
  209. package/src/features/sketch/sketchSolver2D/constraintDefinitions.js +704 -0
  210. package/src/features/sketch/sketchSolver2D/mathHelpersMod.js +307 -0
  211. package/src/features/spline/SplineEditorSession.js +988 -0
  212. package/src/features/spline/SplineFeature.js +1388 -0
  213. package/src/features/spline/splineUtils.js +218 -0
  214. package/src/features/sweep/SweepFeature.js +110 -0
  215. package/src/features/transform/TransformFeature.js +152 -0
  216. package/src/features/tube/TubeFeature.js +635 -0
  217. package/src/fs.proxy.js +625 -0
  218. package/src/idbStorage.js +254 -0
  219. package/src/index.js +12 -0
  220. package/src/main.js +15 -0
  221. package/src/metadataManager.js +64 -0
  222. package/src/path.proxy.js +277 -0
  223. package/src/plugins/ghLoader.worker.js +151 -0
  224. package/src/plugins/pluginManager.js +286 -0
  225. package/src/pmi/PMIViewsManager.js +134 -0
  226. package/src/services/componentLibrary.js +198 -0
  227. package/src/tests/ConsoleCapture.js +189 -0
  228. package/src/tests/S7-diagnostics-2025-12-23T18-37-23-570Z.json +630 -0
  229. package/src/tests/browserTests.js +597 -0
  230. package/src/tests/debugBoolean.js +225 -0
  231. package/src/tests/partFiles/badBoolean.json +957 -0
  232. package/src/tests/partFiles/extrudeTest.json +88 -0
  233. package/src/tests/partFiles/filletFail.json +58 -0
  234. package/src/tests/partFiles/import_TEst.part.part.json +646 -0
  235. package/src/tests/partFiles/sheetMetalHem.BREP.json +734 -0
  236. package/src/tests/test_boolean_subtract.js +27 -0
  237. package/src/tests/test_chamfer.js +17 -0
  238. package/src/tests/test_extrudeFace.js +24 -0
  239. package/src/tests/test_fillet.js +17 -0
  240. package/src/tests/test_fillet_nonClosed.js +45 -0
  241. package/src/tests/test_filletsMoreDifficult.js +46 -0
  242. package/src/tests/test_history_features_basic.js +149 -0
  243. package/src/tests/test_hole.js +282 -0
  244. package/src/tests/test_mirror.js +16 -0
  245. package/src/tests/test_offsetShellGrouping.js +85 -0
  246. package/src/tests/test_plane.js +4 -0
  247. package/src/tests/test_primitiveCone.js +11 -0
  248. package/src/tests/test_primitiveCube.js +7 -0
  249. package/src/tests/test_primitiveCylinder.js +8 -0
  250. package/src/tests/test_primitivePyramid.js +9 -0
  251. package/src/tests/test_primitiveSphere.js +17 -0
  252. package/src/tests/test_primitiveTorus.js +21 -0
  253. package/src/tests/test_pushFace.js +126 -0
  254. package/src/tests/test_sheetMetalContourFlange.js +125 -0
  255. package/src/tests/test_sheetMetal_features.js +80 -0
  256. package/src/tests/test_sketch_openLoop.js +45 -0
  257. package/src/tests/test_solidMetrics.js +58 -0
  258. package/src/tests/test_stlLoader.js +1889 -0
  259. package/src/tests/test_sweepFace.js +55 -0
  260. package/src/tests/test_tube.js +45 -0
  261. package/src/tests/test_tube_closedLoop.js +67 -0
  262. package/src/tests/tests.js +493 -0
  263. package/src/tools/assemblyConstraintDialogCapturePage.js +56 -0
  264. package/src/tools/dialogCapturePageFactory.js +227 -0
  265. package/src/tools/featureDialogCapturePage.js +47 -0
  266. package/src/tools/pmiAnnotationDialogCapturePage.js +60 -0
  267. package/src/utils/axisHelpers.js +99 -0
  268. package/src/utils/deepClone.js +69 -0
  269. package/src/utils/geometryTolerance.js +37 -0
  270. package/src/utils/normalizeTypeString.js +8 -0
  271. package/src/utils/xformMath.js +51 -0
@@ -0,0 +1,353 @@
1
+ import { annotationRegistry } from './AnnotationRegistry.js';
2
+ import { deepClone } from '../../utils/deepClone.js';
3
+ import { normalizeTypeString } from '../../utils/normalizeTypeString.js';
4
+ import { HistoryCollectionBase } from '../../core/entities/HistoryCollectionBase.js';
5
+
6
+ const RESERVED_INPUT_KEYS = new Set(['type', 'persistentData', '__open']);
7
+ const DEFAULT_TYPE = 'annotation';
8
+
9
+ export class AnnotationHistory extends HistoryCollectionBase {
10
+ constructor(pmimode = null) {
11
+ super({ viewer: pmimode?.viewer || null });
12
+ this.pmimode = pmimode || null;
13
+ this.#registerAvailableAnnotations();
14
+ }
15
+
16
+ setPMIMode(pmimode) {
17
+ this.pmimode = pmimode || null;
18
+ if (pmimode?.viewer) {
19
+ this.viewer = pmimode.viewer;
20
+ }
21
+ }
22
+
23
+ load(serializedAnnotations) {
24
+ this.entries = [];
25
+ this._idCounter = 0;
26
+ const list = Array.isArray(serializedAnnotations) ? serializedAnnotations : [];
27
+ for (const raw of list) {
28
+ const entity = this.#hydrateEntity(raw);
29
+ if (!entity) continue;
30
+ this.entries.push(entity);
31
+ this.#bumpIdCounterFrom(entity);
32
+ this.#linkInputParams(entity);
33
+ }
34
+ this.notifyListeners({ reason: 'load', history: this });
35
+ return this.entries;
36
+ }
37
+
38
+ toSerializable() {
39
+ return this.entries.map((entity) => {
40
+ const open = Boolean(entity.runtimeAttributes?.__open);
41
+ const input = deepClone(entity.inputParams || {});
42
+ if (input && typeof input === 'object') {
43
+ delete input.persistentData;
44
+ delete input.__open;
45
+ delete input.__entityRef;
46
+ }
47
+ return {
48
+ type: entity.type || DEFAULT_TYPE,
49
+ inputParams: input,
50
+ persistentData: deepClone(entity.persistentData || {}),
51
+ __open: open || undefined,
52
+ enabled: entity.enabled !== false,
53
+ };
54
+ });
55
+ }
56
+
57
+ get size() {
58
+ return this.entries.length;
59
+ }
60
+
61
+ getEntries() {
62
+ return this.entries.slice();
63
+ }
64
+
65
+ getEntry(index) {
66
+ if (!Number.isInteger(index) || index < 0 || index >= this.entries.length) return null;
67
+ return this.entries[index] || null;
68
+ }
69
+
70
+ findById(entryId) {
71
+ if (entryId == null) return null;
72
+ const target = String(entryId);
73
+ if (!target) return null;
74
+ for (const entity of this.entries) {
75
+ const params = entity?.inputParams;
76
+ const candidate = params?.id ?? entity?.id;
77
+ if (candidate != null && String(candidate) === target) {
78
+ return entity;
79
+ }
80
+ }
81
+ return null;
82
+ }
83
+
84
+ setAnnotationEnabled(annotationId, enabled) {
85
+ const entry = this.findById(annotationId);
86
+ if (!entry) return false;
87
+ const next = enabled !== false;
88
+ const prev = entry.enabled !== false;
89
+ if (prev === next) return false;
90
+ this.#applyEnabledState(entry, next);
91
+ this.notifyListeners({ reason: 'update', entry, history: this });
92
+ return true;
93
+ }
94
+
95
+ createAnnotation(type, initialData = null) {
96
+ const EntityClass = this.#resolveHandler(type);
97
+ if (!EntityClass) return null;
98
+ const entity = new EntityClass({ history: this, registry: this.registry });
99
+ entity.type = EntityClass.entityType || EntityClass.type || normalizeTypeString(type) || DEFAULT_TYPE;
100
+ entity.entityType = entity.type;
101
+ const defaults = this.#defaultsFromSchema(EntityClass);
102
+ const seed = deepClone(initialData || {});
103
+ const params = { ...defaults, ...seed };
104
+ if (!params.type) params.type = entity.type;
105
+ entity.setParams(params);
106
+ entity.setPersistentData(params.persistentData || {});
107
+ delete entity.inputParams.persistentData;
108
+ this.#linkInputParams(entity);
109
+
110
+ if (typeof EntityClass.applyParams === 'function') {
111
+ try {
112
+ const res = EntityClass.applyParams(this.pmimode, entity.inputParams, entity.inputParams) || null;
113
+ if (res && res.paramsPatch && typeof res.paramsPatch === 'object') {
114
+ entity.mergeParams(res.paramsPatch);
115
+ }
116
+ } catch {
117
+ // ignore apply errors
118
+ }
119
+ }
120
+
121
+ const id = entity.inputParams.id || this.generateId(entity.shortName || entity.type || 'ANN');
122
+ entity.setId(id);
123
+ entity.runtimeAttributes.__open = true;
124
+ this.#applyEnabledState(entity, true);
125
+ this.entries.push(entity);
126
+ this.#bumpIdCounterFrom(entity);
127
+ this.notifyListeners({ reason: 'add', entry: entity, history: this });
128
+ return entity;
129
+ }
130
+
131
+ removeAt(index) {
132
+ if (!Number.isInteger(index) || index < 0 || index >= this.entries.length) return null;
133
+ const [entity] = this.entries.splice(index, 1);
134
+ if (!entity) return null;
135
+ this.notifyListeners({ reason: 'remove', entry: entity, history: this });
136
+ return entity;
137
+ }
138
+
139
+ moveUp(index) {
140
+ if (!Number.isInteger(index) || index <= 0 || index >= this.entries.length) return false;
141
+ const [entity] = this.entries.splice(index, 1);
142
+ this.entries.splice(index - 1, 0, entity);
143
+ this.notifyListeners({ reason: 'reorder', entry: entity, history: this });
144
+ return true;
145
+ }
146
+
147
+ moveDown(index) {
148
+ if (!Number.isInteger(index) || index < 0 || index >= this.entries.length - 1) return false;
149
+ const [entity] = this.entries.splice(index, 1);
150
+ this.entries.splice(index + 1, 0, entity);
151
+ this.notifyListeners({ reason: 'reorder', entry: entity, history: this });
152
+ return true;
153
+ }
154
+
155
+ clear() {
156
+ this.entries = [];
157
+ this._idCounter = 0;
158
+ this.notifyListeners({ reason: 'clear', history: this });
159
+ }
160
+
161
+ generateId(typeHint = 'ANN') {
162
+ const safeHint = normalizeTypeString(typeHint) || 'ann';
163
+ const prefix = safeHint.replace(/[^a-z0-9]/gi, '').toUpperCase() || 'ANN';
164
+ const existing = new Set(this.entries.map((entity, i) => {
165
+ const params = entity?.inputParams;
166
+ if (params?.id) return String(params.id);
167
+ if (entity?.id != null) return String(entity.id);
168
+ return `ANN${i + 1}`;
169
+ }));
170
+ let candidate = '';
171
+ do {
172
+ this._idCounter += 1;
173
+ candidate = `${prefix}${this._idCounter}`;
174
+ } while (existing.has(candidate));
175
+ return candidate;
176
+ }
177
+
178
+ addListener(listener) {
179
+ if (typeof listener !== 'function') return () => {};
180
+ this._listeners.add(listener);
181
+ return () => {
182
+ try { this._listeners.delete(listener); } catch {
183
+ // ignore
184
+ }
185
+ };
186
+ }
187
+
188
+ removeListener(listener) {
189
+ if (typeof listener !== 'function') return;
190
+ try { this._listeners.delete(listener); } catch {
191
+ // ignore
192
+ }
193
+ }
194
+
195
+ notifyListeners(payload = {}) {
196
+ if (!(this._listeners instanceof Set)) return;
197
+ for (const fn of Array.from(this._listeners)) {
198
+ try { fn(payload, this); } catch {
199
+ // ignore listener errors
200
+ }
201
+ }
202
+ }
203
+
204
+ #hydrateEntity(raw) {
205
+ const source = raw && typeof raw === 'object' ? raw : {};
206
+ const type = normalizeTypeString(source.type || source.inputParams?.type || DEFAULT_TYPE) || DEFAULT_TYPE;
207
+ const EntityClass = this.#resolveHandler(type);
208
+ if (!EntityClass) return null;
209
+ const entity = new EntityClass({ history: this, registry: this.registry });
210
+ entity.type = EntityClass.entityType || EntityClass.type || type;
211
+ entity.entityType = entity.type;
212
+
213
+ const params = this.#cloneWithoutReserved(source.inputParams || source);
214
+ if (!params.type) params.type = entity.type;
215
+ entity.setParams(params);
216
+ entity.setPersistentData(deepClone(source.persistentData || {}));
217
+ delete entity.inputParams.persistentData;
218
+
219
+ if (typeof EntityClass.applyParams === 'function') {
220
+ try {
221
+ const res = EntityClass.applyParams(this.pmimode, entity.inputParams, entity.inputParams) || null;
222
+ if (res && res.paramsPatch && typeof res.paramsPatch === 'object') {
223
+ entity.mergeParams(res.paramsPatch);
224
+ }
225
+ } catch {
226
+ // ignore
227
+ }
228
+ }
229
+
230
+ const id = entity.inputParams.id || source.id || this.generateId(entity.shortName || entity.type);
231
+ entity.setId(id);
232
+ entity.runtimeAttributes.__open = Boolean(source.__open);
233
+ this.#applyEnabledState(entity, source.enabled);
234
+ this.#linkInputParams(entity);
235
+ return entity;
236
+ }
237
+
238
+ #linkInputParams(entity) {
239
+ if (!entity) return;
240
+ if (!entity.runtimeAttributes || typeof entity.runtimeAttributes !== 'object') {
241
+ entity.runtimeAttributes = {};
242
+ }
243
+ const params = entity.inputParams || {};
244
+ const descriptor = { configurable: true, enumerable: false };
245
+
246
+ if (!Object.prototype.hasOwnProperty.call(params, '__entityRef')) {
247
+ Object.defineProperty(params, '__entityRef', { ...descriptor, value: entity });
248
+ }
249
+
250
+ if (!Object.prototype.hasOwnProperty.call(params, 'persistentData')) {
251
+ Object.defineProperty(params, 'persistentData', {
252
+ ...descriptor,
253
+ get: () => entity.persistentData,
254
+ set: (value) => {
255
+ const next = (value && typeof value === 'object') ? value : {};
256
+ entity.setPersistentData(next);
257
+ },
258
+ });
259
+ }
260
+
261
+ if (!Object.prototype.hasOwnProperty.call(params, '__open')) {
262
+ Object.defineProperty(params, '__open', {
263
+ ...descriptor,
264
+ get: () => Boolean(entity.runtimeAttributes.__open),
265
+ set: (value) => {
266
+ entity.runtimeAttributes.__open = Boolean(value);
267
+ },
268
+ });
269
+ }
270
+
271
+ params.type = entity.type || params.type || DEFAULT_TYPE;
272
+ if (params.id == null && entity.id != null) {
273
+ params.id = entity.id;
274
+ }
275
+ }
276
+
277
+ #defaultsFromSchema(EntityClass) {
278
+ const out = {};
279
+ const schema = EntityClass?.inputParamsSchema;
280
+ if (!schema || typeof schema !== 'object') return out;
281
+ for (const key of Object.keys(schema)) {
282
+ if (RESERVED_INPUT_KEYS.has(key)) continue;
283
+ const def = schema[key];
284
+ if (!def || typeof def !== 'object') continue;
285
+ if ('defaultResolver' in def && typeof def.defaultResolver === 'function') {
286
+ try {
287
+ const resolved = def.defaultResolver({ pmimode: this.pmimode, handler: EntityClass });
288
+ if (resolved !== undefined) {
289
+ out[key] = deepClone(resolved);
290
+ continue;
291
+ }
292
+ } catch {
293
+ // ignore resolver errors
294
+ }
295
+ }
296
+ if ('default_value' in def) {
297
+ out[key] = deepClone(def.default_value);
298
+ }
299
+ }
300
+ return out;
301
+ }
302
+
303
+ #cloneWithoutReserved(obj) {
304
+ const out = {};
305
+ if (!obj || typeof obj !== 'object') return out;
306
+ for (const key of Object.keys(obj)) {
307
+ if (RESERVED_INPUT_KEYS.has(key)) continue;
308
+ out[key] = deepClone(obj[key]);
309
+ }
310
+ return out;
311
+ }
312
+
313
+ #registerAvailableAnnotations() {
314
+ const list = annotationRegistry.list();
315
+ for (const Handler of list) {
316
+ try { this.registry.register(Handler); } catch {
317
+ // ignore duplicate registrations
318
+ }
319
+ }
320
+ }
321
+
322
+ #resolveHandler(type) {
323
+ const handler = annotationRegistry.getSafe?.(type) || null;
324
+ if (handler) {
325
+ try { this.registry.register(handler); } catch {
326
+ // ignore duplicate
327
+ }
328
+ }
329
+ return handler;
330
+ }
331
+
332
+ #bumpIdCounterFrom(entity) {
333
+ const id = entity?.inputParams?.id || entity?.id;
334
+ if (!id) return;
335
+ const match = String(id).match(/(\d+)$/);
336
+ if (!match) return;
337
+ const num = parseInt(match[1], 10);
338
+ if (Number.isFinite(num) && num > this._idCounter) {
339
+ this._idCounter = num;
340
+ }
341
+ }
342
+
343
+ #applyEnabledState(entity, value) {
344
+ if (!entity) return false;
345
+ const enabled = value !== false;
346
+ entity.enabled = enabled;
347
+ if (!entity.runtimeAttributes || typeof entity.runtimeAttributes !== 'object') {
348
+ entity.runtimeAttributes = {};
349
+ }
350
+ entity.runtimeAttributes.__enabled = enabled;
351
+ return enabled;
352
+ }
353
+ }
@@ -0,0 +1,90 @@
1
+ // Simple registry for PMI annotation types. Allows lookup by type string or aliases.
2
+
3
+ import { LinearDimensionAnnotation } from './dimensions/LinearDimensionAnnotation.js';
4
+ import { RadialDimensionAnnotation } from './dimensions/RadialDimensionAnnotation.js';
5
+ import { AngleDimensionAnnotation } from './dimensions/AngleDimensionAnnotation.js';
6
+ import { LeaderAnnotation } from './dimensions/LeaderAnnotation.js';
7
+ import { NoteAnnotation } from './dimensions/NoteAnnotation.js';
8
+ import { ExplodeBodyAnnotation } from './dimensions/ExplodeBodyAnnotation.js';
9
+ import { HoleCalloutAnnotation } from './dimensions/HoleCalloutAnnotation.js';
10
+
11
+ const normalizeKey = (name) => {
12
+ if (!name && name !== 0) return '';
13
+ return String(name).trim().toLowerCase();
14
+ };
15
+
16
+ class AnnotationRegistry {
17
+ constructor() {
18
+ this._map = new Map();
19
+ this._aliases = new Map();
20
+ }
21
+
22
+ register(handler) {
23
+ if (!handler) return;
24
+ if (typeof handler === 'function') {
25
+ const ctor = handler;
26
+ if (!ctor.shortName) {
27
+ ctor.shortName = ctor.featureShortName || ctor.name || ctor.type || 'ANN';
28
+ }
29
+ if (!ctor.longName) {
30
+ ctor.longName = ctor.featureName || ctor.name || ctor.shortName || ctor.type || 'Annotation';
31
+ }
32
+ }
33
+ const typeKey = normalizeKey(
34
+ handler.type
35
+ || handler.shortName
36
+ || handler.featureShortName
37
+ || handler.name
38
+ || handler.longName
39
+ || handler.featureName,
40
+ );
41
+ if (!typeKey) return;
42
+ this._map.set(typeKey, handler);
43
+
44
+ if (Array.isArray(handler.aliases)) {
45
+ for (const alias of handler.aliases) {
46
+ const aliasKey = normalizeKey(alias);
47
+ if (aliasKey) this._aliases.set(aliasKey, handler);
48
+ }
49
+ }
50
+ }
51
+
52
+ get(name) {
53
+ const key = normalizeKey(name);
54
+ if (!key) {
55
+ throw new Error('Annotation type must be a non-empty string');
56
+ }
57
+ const handler = this._map.get(key) || this._aliases.get(key);
58
+ if (!handler) {
59
+ throw new Error(`Annotation type "${name}" is not registered.`);
60
+ }
61
+ return handler;
62
+ }
63
+
64
+ getSafe(name) {
65
+ try {
66
+ return this.get(name);
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+
72
+ has(name) {
73
+ return !!this.getSafe(name);
74
+ }
75
+
76
+ list() {
77
+ return Array.from(this._map.values());
78
+ }
79
+ }
80
+
81
+ export const annotationRegistry = new AnnotationRegistry();
82
+
83
+ // Register built-in annotation handlers once
84
+ annotationRegistry.register(LinearDimensionAnnotation);
85
+ annotationRegistry.register(RadialDimensionAnnotation);
86
+ annotationRegistry.register(AngleDimensionAnnotation);
87
+ annotationRegistry.register(LeaderAnnotation);
88
+ annotationRegistry.register(NoteAnnotation);
89
+ annotationRegistry.register(ExplodeBodyAnnotation);
90
+ annotationRegistry.register(HoleCalloutAnnotation);
@@ -0,0 +1,269 @@
1
+ // BaseAnnotation.js
2
+ // Base class for all PMI annotations built on the shared list entity foundation.
3
+
4
+ import * as THREE from 'three';
5
+ import { ListEntityBase } from '../../core/entities/ListEntityBase.js';
6
+
7
+ export class BaseAnnotation extends ListEntityBase {
8
+ static entityType = 'annotation';
9
+ static shortName = 'ANN';
10
+ static longName = 'Annotation';
11
+ static inputParamsSchema = {};
12
+
13
+ constructor(opts = {}) {
14
+ super({ history: opts.history || null, registry: opts.registry || null });
15
+ this.resultArtifacts = [];
16
+ }
17
+
18
+ async run(renderingContext) {
19
+ // Base implementation - subclasses should override
20
+ // renderingContext contains: { pmimode, group, idx, ctx }
21
+ console.warn(`BaseAnnotation.run() not implemented for ${this.constructor.name}`);
22
+ return [];
23
+ }
24
+
25
+ // Helper methods that annotations can use
26
+ getScene() {
27
+ const partHistory = this.renderingContext?.pmimode?.viewer?.partHistory;
28
+ return partHistory?.scene || null;
29
+ }
30
+
31
+ getObjectByName(name) {
32
+ const scene = this.getScene();
33
+ return scene ? scene.getObjectByName(name) : null;
34
+ }
35
+
36
+ // Schema helpers mirror feature engine: schema drives UI, no extra per-ann plumbing
37
+ static getSchema(pmimode, ann) {
38
+ const schema = {};
39
+ const params = {};
40
+ const input = ann || {};
41
+
42
+ for (const key in this.inputParamsSchema) {
43
+ if (!Object.prototype.hasOwnProperty.call(this.inputParamsSchema, key)) continue;
44
+ const def = this.inputParamsSchema[key] || {};
45
+ const clonedDef = { ...def };
46
+ const currentValue = Object.prototype.hasOwnProperty.call(input, key)
47
+ ? __cloneValue(input[key])
48
+ : __cloneValue(def.default_value);
49
+ if (clonedDef && Object.prototype.hasOwnProperty.call(clonedDef, 'default_value')) {
50
+ clonedDef.default_value = __cloneValue(currentValue);
51
+ }
52
+ schema[key] = clonedDef;
53
+ params[key] = currentValue;
54
+ }
55
+
56
+ return { schema, params };
57
+ }
58
+
59
+ static applyParams(pmimode, ann, params) {
60
+ const sanitized = sanitizeAnnotationParams(this.inputParamsSchema, params, ann);
61
+ Object.assign(ann, sanitized);
62
+ return { paramsPatch: {} };
63
+ }
64
+
65
+ static ensurePersistentData(ann) {
66
+ if (!ann || typeof ann !== 'object') return null;
67
+ if (!ann.persistentData || typeof ann.persistentData !== 'object') {
68
+ ann.persistentData = {};
69
+ }
70
+ return ann.persistentData;
71
+ }
72
+
73
+ /**
74
+ * Shared label-drag helper. Creates a drag plane and wires pointer events so subclasses only
75
+ * need to provide the plane and what to do on drag.
76
+ */
77
+ static dragLabelOnPlane(pmimode, ctx, options = {}) {
78
+ if (!ctx || typeof options.makePlane !== 'function') return;
79
+ const plane = options.makePlane();
80
+ if (!plane || (!(plane instanceof THREE.Plane) && !plane.isPlane)) return;
81
+ const target = options.eventTarget || window;
82
+ const viewer = pmimode?.viewer;
83
+
84
+ const onMove = (ev) => {
85
+ const ray = ctx.raycastFromEvent ? ctx.raycastFromEvent(ev) : null;
86
+ if (!ray) return;
87
+ const hit = new THREE.Vector3();
88
+ const intersected = ctx.intersectPlane
89
+ ? ctx.intersectPlane(ray, plane, hit)
90
+ : ray.intersectPlane(plane, hit);
91
+ if (!intersected) return;
92
+ if (typeof options.onDrag === 'function') {
93
+ options.onDrag(hit, ev, plane);
94
+ }
95
+ };
96
+
97
+ const onUp = (ev) => {
98
+ try { target.removeEventListener('pointermove', onMove, true); } catch { }
99
+ try { target.removeEventListener('pointerup', onUp, true); } catch { }
100
+ if (options.suspendControls === true) {
101
+ try { if (viewer?.controls) viewer.controls.enabled = true; } catch { }
102
+ }
103
+ try { pmimode?.hideDragPlaneHelper?.(); } catch { }
104
+ if (typeof options.onEnd === 'function') {
105
+ try { options.onEnd(ev); } catch { /* ignore */ }
106
+ }
107
+ if (options.preventDefault !== false) {
108
+ try { ev.preventDefault(); ev.stopImmediatePropagation?.(); ev.stopPropagation(); } catch { }
109
+ }
110
+ };
111
+
112
+ try { pmimode?.showDragPlaneHelper?.(plane); } catch { }
113
+ if (options.suspendControls === true) {
114
+ try { if (viewer?.controls) viewer.controls.enabled = false; } catch { }
115
+ }
116
+
117
+ try { target.addEventListener('pointermove', onMove, true); } catch { }
118
+ try { target.addEventListener('pointerup', onUp, true); } catch { }
119
+ }
120
+
121
+ onIdChanged() {
122
+ if (!this.inputParams || typeof this.inputParams !== 'object') {
123
+ this.inputParams = {};
124
+ }
125
+ this.inputParams.id = this.id;
126
+ }
127
+
128
+ onParamsChanged() {
129
+ if (!this.inputParams || typeof this.inputParams !== 'object') {
130
+ this.inputParams = {};
131
+ }
132
+ if (!this.inputParams.id) {
133
+ this.inputParams.id = this.id;
134
+ }
135
+ if (!this.inputParams.type) {
136
+ this.inputParams.type = this.type || this.entityType || 'annotation';
137
+ }
138
+ }
139
+
140
+ onPersistentDataChanged() {
141
+ // Ensure persistentData is always an object for UI bindings
142
+ if (!this.persistentData || typeof this.persistentData !== 'object') {
143
+ this.persistentData = {};
144
+ }
145
+ }
146
+ }
147
+
148
+ function sanitizeAnnotationParams(schema, rawParams, ann) {
149
+ const sanitized = {};
150
+ const params = rawParams && typeof rawParams === 'object' ? rawParams : {};
151
+
152
+ for (const key in schema) {
153
+ if (!Object.prototype.hasOwnProperty.call(schema, key)) continue;
154
+ const def = schema[key] || {};
155
+ if (def.readOnly) {
156
+ // Preserve existing read-only values or fall back to defaults
157
+ if (ann && Object.prototype.hasOwnProperty.call(ann, key)) {
158
+ sanitized[key] = ann[key];
159
+ } else {
160
+ sanitized[key] = __cloneValue(def.default_value);
161
+ }
162
+ continue;
163
+ }
164
+ const value = params[key];
165
+ const hasExisting = ann && Object.prototype.hasOwnProperty.call(ann, key);
166
+ if (value === undefined) {
167
+ sanitized[key] = hasExisting ? __cloneValue(ann[key]) : __cloneValue(def.default_value);
168
+ continue;
169
+ }
170
+ switch (def.type) {
171
+ case 'number': {
172
+ const num = Number(value);
173
+ sanitized[key] = Number.isFinite(num)
174
+ ? num
175
+ : (hasExisting && Number.isFinite(ann[key]) ? Number(ann[key])
176
+ : (Number.isFinite(def.default_value) ? def.default_value : 0));
177
+ break;
178
+ }
179
+ case 'boolean':
180
+ sanitized[key] = value === 'false' ? false : Boolean(value);
181
+ break;
182
+ case 'options': {
183
+ const opts = Array.isArray(def.options) ? def.options : [];
184
+ const asString = value == null ? '' : String(value);
185
+ if (opts.includes(asString)) {
186
+ sanitized[key] = asString;
187
+ } else if (hasExisting && opts.includes(ann[key])) {
188
+ sanitized[key] = ann[key];
189
+ } else {
190
+ sanitized[key] = opts.includes(def.default_value) ? def.default_value : (opts[0] || '');
191
+ }
192
+ break;
193
+ }
194
+ case 'reference_selection': {
195
+ if (def.multiple) {
196
+ const arr = Array.isArray(value) ? value : (value ? [value] : []);
197
+ sanitized[key] = arr.map((v) => (v == null ? '' : String(v))).filter((s) => s.length);
198
+ if (!sanitized[key].length && hasExisting && Array.isArray(ann[key])) {
199
+ sanitized[key] = ann[key].slice();
200
+ }
201
+ } else {
202
+ sanitized[key] = value == null ? (hasExisting ? String(ann[key] ?? '') : '') : String(value);
203
+ }
204
+ break;
205
+ }
206
+ case 'textarea':
207
+ case 'string':
208
+ sanitized[key] = value == null ? (hasExisting ? String(ann[key] ?? '') : '') : String(value);
209
+ break;
210
+ case 'object':
211
+ sanitized[key] = (value && typeof value === 'object') ? __cloneValue(value) : __cloneValue(def.default_value);
212
+ break;
213
+ case 'transform':
214
+ sanitized[key] = normalizeTransform(value, def.default_value);
215
+ break;
216
+ case 'vec3':
217
+ sanitized[key] = normalizeVec3(value, def.default_value);
218
+ break;
219
+ default:
220
+ sanitized[key] = __cloneValue(value);
221
+ break;
222
+ }
223
+ }
224
+
225
+ return sanitized;
226
+ }
227
+
228
+ function normalizeTransform(value, fallback) {
229
+ const raw = value && typeof value === 'object' ? value : (fallback || {});
230
+ const toArray = (src, defaults, len = 3) => {
231
+ const arr = Array.isArray(src) ? src : (defaults && Array.isArray(defaults) ? defaults : []);
232
+ const out = [];
233
+ for (let i = 0; i < len; i += 1) {
234
+ const v = arr[i];
235
+ const n = Number(v);
236
+ out[i] = Number.isFinite(n) ? n : 0;
237
+ }
238
+ return out;
239
+ };
240
+ return {
241
+ position: toArray(raw.position, fallback?.position || [0, 0, 0]),
242
+ rotationEuler: toArray(raw.rotationEuler, fallback?.rotationEuler || [0, 0, 0]),
243
+ scale: toArray(raw.scale, fallback?.scale || [1, 1, 1]),
244
+ };
245
+ }
246
+
247
+ function normalizeVec3(value, fallback) {
248
+ const source = Array.isArray(value)
249
+ ? value
250
+ : (value && typeof value === 'object' ? [value.x, value.y, value.z] : fallback);
251
+ if (!Array.isArray(source)) return [0, 0, 0];
252
+ const out = [];
253
+ for (let i = 0; i < 3; i += 1) {
254
+ const n = Number(source[i]);
255
+ out[i] = Number.isFinite(n) ? n : 0;
256
+ }
257
+ return out;
258
+ }
259
+
260
+ function __cloneValue(value) {
261
+ if (value == null) return value;
262
+ if (Array.isArray(value)) return value.map((v) => __cloneValue(v));
263
+ if (typeof value === 'object') {
264
+ const out = {};
265
+ for (const k of Object.keys(value)) out[k] = __cloneValue(value[k]);
266
+ return out;
267
+ }
268
+ return value;
269
+ }