brep-io-kernel 1.0.0 → 1.0.11

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 (134) hide show
  1. package/README.md +16 -3
  2. package/package.json +5 -1
  3. package/dist-kernel/help/CONTRIBUTING.html +0 -248
  4. package/dist-kernel/help/LICENSE.html +0 -248
  5. package/dist-kernel/help/MODELING.png +0 -0
  6. package/dist-kernel/help/PMI.png +0 -0
  7. package/dist-kernel/help/SKETCH.png +0 -0
  8. package/dist-kernel/help/assembly-constraints__Coincident_Constraint_dialog.png +0 -0
  9. package/dist-kernel/help/assembly-constraints___Angle_Constraint_dialog.png +0 -0
  10. package/dist-kernel/help/assembly-constraints___Distance_Constraint_dialog.png +0 -0
  11. package/dist-kernel/help/assembly-constraints___Fixed_Constraint_dialog.png +0 -0
  12. package/dist-kernel/help/assembly-constraints___Parallel_Constraint_dialog.png +0 -0
  13. package/dist-kernel/help/assembly-constraints___Touch_Align_Constraint_dialog.png +0 -0
  14. package/dist-kernel/help/assembly-constraints__angle-constraint.html +0 -248
  15. package/dist-kernel/help/assembly-constraints__coincident-constraint.html +0 -248
  16. package/dist-kernel/help/assembly-constraints__distance-constraint.html +0 -248
  17. package/dist-kernel/help/assembly-constraints__fixed-constraint.html +0 -248
  18. package/dist-kernel/help/assembly-constraints__parallel-constraint.html +0 -248
  19. package/dist-kernel/help/assembly-constraints__solver.html +0 -248
  20. package/dist-kernel/help/assembly-constraints__touch-align-constraint.html +0 -248
  21. package/dist-kernel/help/brep-api.html +0 -263
  22. package/dist-kernel/help/brep-kernel.html +0 -258
  23. package/dist-kernel/help/brep-model.html +0 -248
  24. package/dist-kernel/help/cylindrical-face-radius-embedding.html +0 -290
  25. package/dist-kernel/help/dialog-screenshots.html +0 -248
  26. package/dist-kernel/help/extruded-sketch-radius-embedding.html +0 -336
  27. package/dist-kernel/help/features__Assembly_Component_dialog.png +0 -0
  28. package/dist-kernel/help/features__Boolean_dialog.png +0 -0
  29. package/dist-kernel/help/features__Chamfer_dialog.png +0 -0
  30. package/dist-kernel/help/features__Datium_dialog.png +0 -0
  31. package/dist-kernel/help/features__Extrude_dialog.png +0 -0
  32. package/dist-kernel/help/features__Fillet_dialog.png +0 -0
  33. package/dist-kernel/help/features__Helix_dialog.png +0 -0
  34. package/dist-kernel/help/features__Hole_dialog.png +0 -0
  35. package/dist-kernel/help/features__Image_Heightmap_Solid_dialog.png +0 -0
  36. package/dist-kernel/help/features__Image_to_Face_dialog.png +0 -0
  37. package/dist-kernel/help/features__Import_3D_Model_dialog.png +0 -0
  38. package/dist-kernel/help/features__Loft_dialog.png +0 -0
  39. package/dist-kernel/help/features__Mirror_dialog.png +0 -0
  40. package/dist-kernel/help/features__Offset_Shell_dialog.png +0 -0
  41. package/dist-kernel/help/features__Overlap_Cleanup_dialog.png +0 -0
  42. package/dist-kernel/help/features__Pattern_Linear_dialog.png +0 -0
  43. package/dist-kernel/help/features__Pattern_Radial_dialog.png +0 -0
  44. package/dist-kernel/help/features__Pattern_dialog.png +0 -0
  45. package/dist-kernel/help/features__Plane_dialog.png +0 -0
  46. package/dist-kernel/help/features__Primitive_Cone_dialog.png +0 -0
  47. package/dist-kernel/help/features__Primitive_Cube_dialog.png +0 -0
  48. package/dist-kernel/help/features__Primitive_Cylinder_dialog.png +0 -0
  49. package/dist-kernel/help/features__Primitive_Pyramid_dialog.png +0 -0
  50. package/dist-kernel/help/features__Primitive_Sphere_dialog.png +0 -0
  51. package/dist-kernel/help/features__Primitive_Torus_dialog.png +0 -0
  52. package/dist-kernel/help/features__Remesh_dialog.png +0 -0
  53. package/dist-kernel/help/features__Revolve_dialog.png +0 -0
  54. package/dist-kernel/help/features__Sheet_Metal_Contour_Flange_dialog.png +0 -0
  55. package/dist-kernel/help/features__Sheet_Metal_Cutout_dialog.png +0 -0
  56. package/dist-kernel/help/features__Sheet_Metal_Flange_dialog.png +0 -0
  57. package/dist-kernel/help/features__Sheet_Metal_Tab_dialog.png +0 -0
  58. package/dist-kernel/help/features__Sketch_dialog.png +0 -0
  59. package/dist-kernel/help/features__Spline_dialog.png +0 -0
  60. package/dist-kernel/help/features__Sweep_dialog.png +0 -0
  61. package/dist-kernel/help/features__Transform_dialog.png +0 -0
  62. package/dist-kernel/help/features__Tube_dialog.png +0 -0
  63. package/dist-kernel/help/features__assembly-component.html +0 -248
  64. package/dist-kernel/help/features__boolean.html +0 -248
  65. package/dist-kernel/help/features__chamfer.html +0 -248
  66. package/dist-kernel/help/features__datium.html +0 -248
  67. package/dist-kernel/help/features__datum.html +0 -248
  68. package/dist-kernel/help/features__extrude.html +0 -248
  69. package/dist-kernel/help/features__fillet.html +0 -248
  70. package/dist-kernel/help/features__helix.html +0 -248
  71. package/dist-kernel/help/features__hole.html +0 -248
  72. package/dist-kernel/help/features__image-heightmap-solid.html +0 -248
  73. package/dist-kernel/help/features__image-to-face-2D_dialog.png +0 -0
  74. package/dist-kernel/help/features__image-to-face-3D_dialog.png +0 -0
  75. package/dist-kernel/help/features__image-to-face.html +0 -248
  76. package/dist-kernel/help/features__import-3d-model.html +0 -248
  77. package/dist-kernel/help/features__index.html +0 -248
  78. package/dist-kernel/help/features__loft.html +0 -248
  79. package/dist-kernel/help/features__mirror.html +0 -248
  80. package/dist-kernel/help/features__offset-shell.html +0 -248
  81. package/dist-kernel/help/features__pattern-linear.html +0 -248
  82. package/dist-kernel/help/features__pattern-radial.html +0 -248
  83. package/dist-kernel/help/features__pattern.html +0 -248
  84. package/dist-kernel/help/features__plane.html +0 -248
  85. package/dist-kernel/help/features__primitive-cone.html +0 -248
  86. package/dist-kernel/help/features__primitive-cube.html +0 -248
  87. package/dist-kernel/help/features__primitive-cylinder.html +0 -248
  88. package/dist-kernel/help/features__primitive-pyramid.html +0 -248
  89. package/dist-kernel/help/features__primitive-sphere.html +0 -248
  90. package/dist-kernel/help/features__primitive-torus.html +0 -248
  91. package/dist-kernel/help/features__remesh.html +0 -248
  92. package/dist-kernel/help/features__revolve.html +0 -248
  93. package/dist-kernel/help/features__sheet-metal-contour-flange.html +0 -248
  94. package/dist-kernel/help/features__sheet-metal-flange.html +0 -248
  95. package/dist-kernel/help/features__sheet-metal-tab.html +0 -248
  96. package/dist-kernel/help/features__sketch.html +0 -248
  97. package/dist-kernel/help/features__spline.html +0 -248
  98. package/dist-kernel/help/features__sweep.html +0 -248
  99. package/dist-kernel/help/features__transform.html +0 -248
  100. package/dist-kernel/help/features__tube.html +0 -248
  101. package/dist-kernel/help/file-formats.html +0 -248
  102. package/dist-kernel/help/getting-started.html +0 -248
  103. package/dist-kernel/help/highlights.html +0 -248
  104. package/dist-kernel/help/history-systems.html +0 -248
  105. package/dist-kernel/help/how-it-works.html +0 -248
  106. package/dist-kernel/help/index.html +0 -862
  107. package/dist-kernel/help/input-params-schema.html +0 -363
  108. package/dist-kernel/help/inspector-improvements.html +0 -248
  109. package/dist-kernel/help/inspector.html +0 -248
  110. package/dist-kernel/help/modes__modeling.html +0 -248
  111. package/dist-kernel/help/modes__pmi.html +0 -248
  112. package/dist-kernel/help/modes__sketch.html +0 -248
  113. package/dist-kernel/help/plugins.html +0 -248
  114. package/dist-kernel/help/pmi-annotations__Angle_Dimension_dialog.png +0 -0
  115. package/dist-kernel/help/pmi-annotations__Explode_Body_dialog.png +0 -0
  116. package/dist-kernel/help/pmi-annotations__Hole_Callout_dialog.png +0 -0
  117. package/dist-kernel/help/pmi-annotations__Leader_dialog.png +0 -0
  118. package/dist-kernel/help/pmi-annotations__Linear_Dimension_dialog.png +0 -0
  119. package/dist-kernel/help/pmi-annotations__Note_dialog.png +0 -0
  120. package/dist-kernel/help/pmi-annotations__Radial_Dimension_dialog.png +0 -0
  121. package/dist-kernel/help/pmi-annotations__angle-dimension.html +0 -248
  122. package/dist-kernel/help/pmi-annotations__explode-body.html +0 -248
  123. package/dist-kernel/help/pmi-annotations__hole-callout.html +0 -248
  124. package/dist-kernel/help/pmi-annotations__index.html +0 -248
  125. package/dist-kernel/help/pmi-annotations__leader.html +0 -248
  126. package/dist-kernel/help/pmi-annotations__linear-dimension.html +0 -248
  127. package/dist-kernel/help/pmi-annotations__note.html +0 -248
  128. package/dist-kernel/help/pmi-annotations__radial-dimension.html +0 -248
  129. package/dist-kernel/help/search-index.json +0 -464
  130. package/dist-kernel/help/simplified-radial-dimensions.html +0 -298
  131. package/dist-kernel/help/solid-methods.html +0 -359
  132. package/dist-kernel/help/table-of-contents.html +0 -330
  133. package/dist-kernel/help/ui-overview.html +0 -248
  134. package/dist-kernel/help/whats-new.html +0 -248
@@ -1,248 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>Highlights - BREP</title>
7
- <style>
8
- :root{
9
- --bg:#0b0f14; --panel:#0f141b; --text:#d7dde6; --muted:#9aa7b2;
10
- --border:#1b2430; --accent:#5cc8ff; --chip:#121823;
11
- }
12
- *{box-sizing:border-box}
13
- html,body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono','Courier New',monospace}
14
- main{max-width:1100px;margin:0 auto;padding:28px}
15
- h1{margin:0 0 18px;font-size:22px;color:var(--accent);font-weight:700}
16
- .summary{color:var(--muted);margin-bottom:22px}
17
- .card{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:18px;margin:0 0 18px}
18
- .readme{padding:0}
19
- .readme .header{padding:16px 18px;border-bottom:1px solid var(--border)}
20
- .readme .content{padding:18px}
21
- .doc-card{padding:18px}
22
- .doc-nav{margin:0 0 18px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;color:var(--muted)}
23
- .doc-nav-links{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
24
- .doc-nav a{color:var(--accent);font-weight:600}
25
- .doc-list{list-style:none;margin:18px 0;padding:0}
26
- .doc-list li{margin:6px 0}
27
- .doc-list a{color:var(--accent)}
28
- .nav-search{margin-left:auto;position:relative;display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;min-width:240px;max-width:520px}
29
- .search-title{font-weight:700;font-size:14px;margin-bottom:4px}
30
- .search-hint{color:var(--muted);font-size:12px;margin:0}
31
- .search-input-row{display:flex;gap:8px;align-items:center;width:100%}
32
- .search-input{flex:1;border:1px solid var(--border);border-radius:10px;background:var(--chip);color:var(--text);padding:10px 12px}
33
- .search-input:focus{outline:1px solid var(--accent);box-shadow:0 0 0 3px rgba(92,200,255,0.15)}
34
- .search-clear{border:1px solid var(--border);background:var(--panel);color:var(--muted);border-radius:10px;padding:9px 12px;cursor:pointer;font-weight:600}
35
- .search-clear:hover{border-color:var(--accent);color:var(--accent)}
36
- .search-status{color:var(--muted);font-size:12px;margin-top:4px;width:100%;text-align:right}
37
- .search-results{position:absolute;top:100%;right:0;margin-top:10px;border:1px solid var(--border);border-radius:12px;background:var(--panel);max-height:360px;overflow:auto;min-width:320px;max-width:520px;box-shadow:0 12px 30px rgba(0,0,0,0.35);z-index:25}
38
- .search-results ul{list-style:none;margin:0;padding:0}
39
- .search-item{padding:10px 12px;border-bottom:1px solid var(--border)}
40
- .search-item:last-child{border-bottom:none}
41
- .search-item a{display:block;color:var(--accent);font-weight:600}
42
- .search-snippet{color:var(--muted);font-size:13px;margin-top:4px}
43
- .search-results mark{background:rgba(92,200,255,0.2);color:var(--text);border-radius:4px;padding:0 2px}
44
- .prose h1{font-size:24px;margin:0 0 12px}
45
- .prose h2{font-size:18px;margin:18px 0 8px}
46
- .prose h3{font-size:16px;margin:14px 0 6px}
47
- .prose p{margin:0 0 10px}
48
- .prose ul,.prose ol{margin:0 0 10px 18px}
49
- .prose li{margin:4px 0}
50
- .prose code{background:#0d1520;border:1px solid var(--border);padding:1px 5px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono','Courier New',monospace;font-size:12px}
51
- .prose pre{background:#0d1520;border:1px solid var(--border);padding:12px;border-radius:12px;overflow:auto}
52
- .prose a{color:var(--accent)}
53
- .prose img{max-width:100%;height:auto;}
54
- .prose table{border-collapse:collapse;width:100%;margin:16px 0;background:var(--panel);border:1px solid var(--border);border-radius:8px;overflow:hidden}
55
- .prose th,.prose td{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border)}
56
- .prose th{background:var(--chip);color:var(--text);font-weight:600;font-size:13px}
57
- .prose tr:last-child td{border-bottom:none}
58
- .prose tbody tr:hover{background:rgba(92,200,255,0.05)}
59
- .license{
60
- background:var(--panel);border:1px solid var(--border);border-radius:14px;
61
- padding:16px 16px 8px;margin:0 0 18px;
62
- }
63
- .license > h2{margin:0 0 10px;font-size:16px;font-weight:700}
64
- .pkg{
65
- border-top:1px solid var(--border);padding:10px 0;
66
- display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;
67
- }
68
- .pkg:first-of-type{border-top:none}
69
- .pkg .meta{display:flex;gap:10px;flex-wrap:wrap}
70
- .pkg .name{font-weight:600}
71
- .pkg .desc{color:var(--muted);margin-top:2px}
72
- a{color:var(--accent);text-decoration:none} a:hover{text-decoration:underline}
73
- .chip{background:var(--chip);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:12px;color:var(--muted)}
74
- .footer{margin-top:26px;color:var(--muted);font-size:12px}
75
- </style>
76
- </head>
77
- <body>
78
- <main>
79
- <nav class="doc-nav">
80
- <div class="doc-nav-links">
81
- <a href="index.html">Help Home</a><span>&middot;</span><a href="/help/table-of-contents.html">Table of Contents</a><span>&middot;</span><a href="https://github.com/mmiscool/BREP" target="_blank" rel="noopener noreferrer">GitHub</a>
82
- </div>
83
- <div class="nav-search">
84
- <div class="search-input-row">
85
- <input type="search" id="doc-search" class="search-input" placeholder="Search docs... Type at least 2 characters to search" autocomplete="off" spellcheck="false" />
86
- <button type="button" class="search-clear" id="doc-search-clear">Clear</button>
87
- </div>
88
- <div class="search-status" id="doc-search-status"></div>
89
- <div class="search-results" id="doc-search-results" hidden>
90
- <ul id="doc-search-list"></ul>
91
- </div>
92
- </div>
93
- </nav>
94
- <section class="card doc-card">
95
-
96
- <div class="prose">
97
- <h1>Highlights</h1><ul><li>Feature history pipeline with a compact UI to add, edit, and replay features.</li><li>Robust CSG powered by <code>manifold-3d</code> with face-label provenance that survives booleans.</li><li>Mesh-to-BREP conversion groups triangles into faces using normal deflection so selections make sense.</li><li>Mesh repair pipeline includes weld, T-junction fix, overlap removal, hole fill, and normal cleanup.</li><li>Import and export support STL, OBJ, and feature-aware 3MF (with embedded history).</li><li>Primitive solids (cube, sphere, cylinder, cone, torus, pyramid) and common CAD features (sketch/extrude, sweep, loft, revolve, fillet, chamfer, mirror, booleans).</li><li>Modular toolbar with Save, Zoom to Fit, Wireframe toggle, Import/Export, About, and Selection Filter access.</li><li>Browser test runner captures per-test canvas snapshots and shows them in the log dialog.</li><li>Simple plugin system lets you import plugins from GitHub repositories.</li></ul>
98
- </div>
99
- </section>
100
- </main>
101
- <script>
102
- (() => {
103
- const searchInput = document.getElementById("doc-search");
104
- const resultsBox = document.getElementById("doc-search-results");
105
- const resultsList = document.getElementById("doc-search-list");
106
- const statusEl = document.getElementById("doc-search-status");
107
- const clearBtn = document.getElementById("doc-search-clear");
108
- if (!searchInput || !resultsBox || !resultsList || !statusEl || !clearBtn) return;
109
- const indexUrl = "./search-index.json";
110
- let indexPromise = null;
111
- let cachedEntries = null;
112
-
113
- const escapeHtml = (text = "") =>
114
- text
115
- .replace(/&/g, "&amp;")
116
- .replace(/</g, "&lt;")
117
- .replace(/>/g, "&gt;");
118
-
119
- const escapeRegExp = (text = "") => text.replace(/[.*+?^{}()|[]\$]/g, "\$&");
120
-
121
- const highlight = (text, terms) => {
122
- if (!terms.length) return escapeHtml(text);
123
- const pattern = terms.map(escapeRegExp).join("|");
124
- const regex = new RegExp(`(${pattern})`, "gi");
125
- return escapeHtml(text).replace(regex, "<mark>$1</mark>");
126
- };
127
-
128
- const buildSnippet = (entry, terms) => {
129
- const source = entry.content || entry.summary || "";
130
- if (!source) return { plain: "", highlighted: "" };
131
- let hit = source.length;
132
- for (const term of terms) {
133
- const idx = entry.contentLower.indexOf(term);
134
- if (idx !== -1 && idx < hit) hit = idx;
135
- }
136
- if (!Number.isFinite(hit) || hit === source.length) hit = 0;
137
- const start = Math.max(0, hit - 60);
138
- const end = Math.min(source.length, hit + 140);
139
- const snippet = source.slice(start, end);
140
- return { plain: snippet, highlighted: highlight(snippet, terms) };
141
- };
142
-
143
- const updateStatus = (message) => {
144
- statusEl.textContent = message;
145
- };
146
-
147
- const loadIndex = async () => {
148
- if (cachedEntries) return cachedEntries;
149
- if (!indexPromise) {
150
- indexPromise = fetch(indexUrl, { cache: "no-store" })
151
- .then((res) => {
152
- if (!res.ok) throw new Error("Failed to load search index");
153
- return res.json();
154
- })
155
- .then((json) => {
156
- cachedEntries = Array.isArray(json)
157
- ? json.map((entry) => ({
158
- ...entry,
159
- contentLower: (entry.content || "").toLowerCase(),
160
- titleLower: (entry.title || "").toLowerCase(),
161
- }))
162
- : [];
163
- return cachedEntries;
164
- })
165
- .catch((err) => {
166
- console.error("Search index load failed", err);
167
- cachedEntries = [];
168
- throw err;
169
- });
170
- }
171
- return indexPromise;
172
- };
173
-
174
- const renderResults = (entries, terms) => {
175
- if (!terms.length) {
176
- resultsBox.hidden = true;
177
- return;
178
- }
179
- if (!entries.length) {
180
- resultsList.innerHTML = '<li class="search-item"><div class="search-snippet">No matches found.</div></li>';
181
- resultsBox.hidden = false;
182
- return;
183
- }
184
- const rendered = entries
185
- .slice(0, 30)
186
- .map((entry) => {
187
- const snippet = buildSnippet(entry, terms);
188
- const title = highlight(entry.title || entry.href, terms);
189
- const hasFragment = snippet.plain && snippet.plain.trim().length > 0;
190
- const fragment = hasFragment ? `#:~:text=${encodeURIComponent(snippet.plain.trim())}` : "";
191
- const hrefWithFragment = hasFragment
192
- ? (entry.href.includes("#") ? `${entry.href}&:~:text=${encodeURIComponent(snippet.plain.trim())}` : `${entry.href}${fragment}`)
193
- : entry.href;
194
- const snippetHtml = snippet.highlighted ? `<div class="search-snippet">${snippet.highlighted}</div>` : "";
195
- return `<li class="search-item"><a href="${hrefWithFragment}">${title}</a>${snippetHtml}</li>`;
196
- })
197
- .join("");
198
- resultsList.innerHTML = rendered;
199
- resultsBox.hidden = false;
200
- };
201
-
202
- const performSearch = async () => {
203
- const rawQuery = searchInput.value.trim();
204
- if (rawQuery.length < 2) {
205
- updateStatus("Type at least 2 characters to search.");
206
- resultsBox.hidden = true;
207
- return;
208
- }
209
- const terms = rawQuery.toLowerCase().split(/\s+/).filter(Boolean);
210
- updateStatus("Searching...");
211
- try {
212
- const entries = await loadIndex();
213
- const matches = entries.filter((entry) =>
214
- terms.every((term) => entry.contentLower.includes(term) || entry.titleLower.includes(term))
215
- );
216
- renderResults(matches, terms);
217
- updateStatus(matches.length ? `${matches.length} result${matches.length === 1 ? "" : "s"} for "${rawQuery}"` : "No matches found.");
218
- } catch (err) {
219
- updateStatus("Search unavailable (could not load index).");
220
- }
221
- };
222
-
223
- searchInput.addEventListener("input", performSearch);
224
-
225
- searchInput.addEventListener("focus", () => {
226
- if (!cachedEntries) {
227
- loadIndex().catch(() => updateStatus("Search unavailable (could not load index)."));
228
- }
229
- });
230
-
231
- clearBtn.addEventListener("click", () => {
232
- searchInput.value = "";
233
- resultsBox.hidden = true;
234
- updateStatus("Type at least 2 characters to search.");
235
- searchInput.focus();
236
- });
237
-
238
- document.addEventListener("keydown", (evt) => {
239
- if (evt.key === "Escape" && document.activeElement === searchInput) {
240
- searchInput.value = "";
241
- resultsBox.hidden = true;
242
- updateStatus("Type at least 2 characters to search.");
243
- }
244
- });
245
- })();
246
- </script>
247
- </body>
248
- </html>
@@ -1,248 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width,initial-scale=1" />
6
- <title>History Systems - BREP</title>
7
- <style>
8
- :root{
9
- --bg:#0b0f14; --panel:#0f141b; --text:#d7dde6; --muted:#9aa7b2;
10
- --border:#1b2430; --accent:#5cc8ff; --chip:#121823;
11
- }
12
- *{box-sizing:border-box}
13
- html,body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono','Courier New',monospace}
14
- main{max-width:1100px;margin:0 auto;padding:28px}
15
- h1{margin:0 0 18px;font-size:22px;color:var(--accent);font-weight:700}
16
- .summary{color:var(--muted);margin-bottom:22px}
17
- .card{background:var(--panel);border:1px solid var(--border);border-radius:14px;padding:18px;margin:0 0 18px}
18
- .readme{padding:0}
19
- .readme .header{padding:16px 18px;border-bottom:1px solid var(--border)}
20
- .readme .content{padding:18px}
21
- .doc-card{padding:18px}
22
- .doc-nav{margin:0 0 18px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;color:var(--muted)}
23
- .doc-nav-links{display:flex;gap:12px;flex-wrap:wrap;align-items:center}
24
- .doc-nav a{color:var(--accent);font-weight:600}
25
- .doc-list{list-style:none;margin:18px 0;padding:0}
26
- .doc-list li{margin:6px 0}
27
- .doc-list a{color:var(--accent)}
28
- .nav-search{margin-left:auto;position:relative;display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;min-width:240px;max-width:520px}
29
- .search-title{font-weight:700;font-size:14px;margin-bottom:4px}
30
- .search-hint{color:var(--muted);font-size:12px;margin:0}
31
- .search-input-row{display:flex;gap:8px;align-items:center;width:100%}
32
- .search-input{flex:1;border:1px solid var(--border);border-radius:10px;background:var(--chip);color:var(--text);padding:10px 12px}
33
- .search-input:focus{outline:1px solid var(--accent);box-shadow:0 0 0 3px rgba(92,200,255,0.15)}
34
- .search-clear{border:1px solid var(--border);background:var(--panel);color:var(--muted);border-radius:10px;padding:9px 12px;cursor:pointer;font-weight:600}
35
- .search-clear:hover{border-color:var(--accent);color:var(--accent)}
36
- .search-status{color:var(--muted);font-size:12px;margin-top:4px;width:100%;text-align:right}
37
- .search-results{position:absolute;top:100%;right:0;margin-top:10px;border:1px solid var(--border);border-radius:12px;background:var(--panel);max-height:360px;overflow:auto;min-width:320px;max-width:520px;box-shadow:0 12px 30px rgba(0,0,0,0.35);z-index:25}
38
- .search-results ul{list-style:none;margin:0;padding:0}
39
- .search-item{padding:10px 12px;border-bottom:1px solid var(--border)}
40
- .search-item:last-child{border-bottom:none}
41
- .search-item a{display:block;color:var(--accent);font-weight:600}
42
- .search-snippet{color:var(--muted);font-size:13px;margin-top:4px}
43
- .search-results mark{background:rgba(92,200,255,0.2);color:var(--text);border-radius:4px;padding:0 2px}
44
- .prose h1{font-size:24px;margin:0 0 12px}
45
- .prose h2{font-size:18px;margin:18px 0 8px}
46
- .prose h3{font-size:16px;margin:14px 0 6px}
47
- .prose p{margin:0 0 10px}
48
- .prose ul,.prose ol{margin:0 0 10px 18px}
49
- .prose li{margin:4px 0}
50
- .prose code{background:#0d1520;border:1px solid var(--border);padding:1px 5px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono','Courier New',monospace;font-size:12px}
51
- .prose pre{background:#0d1520;border:1px solid var(--border);padding:12px;border-radius:12px;overflow:auto}
52
- .prose a{color:var(--accent)}
53
- .prose img{max-width:100%;height:auto;}
54
- .prose table{border-collapse:collapse;width:100%;margin:16px 0;background:var(--panel);border:1px solid var(--border);border-radius:8px;overflow:hidden}
55
- .prose th,.prose td{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border)}
56
- .prose th{background:var(--chip);color:var(--text);font-weight:600;font-size:13px}
57
- .prose tr:last-child td{border-bottom:none}
58
- .prose tbody tr:hover{background:rgba(92,200,255,0.05)}
59
- .license{
60
- background:var(--panel);border:1px solid var(--border);border-radius:14px;
61
- padding:16px 16px 8px;margin:0 0 18px;
62
- }
63
- .license > h2{margin:0 0 10px;font-size:16px;font-weight:700}
64
- .pkg{
65
- border-top:1px solid var(--border);padding:10px 0;
66
- display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;
67
- }
68
- .pkg:first-of-type{border-top:none}
69
- .pkg .meta{display:flex;gap:10px;flex-wrap:wrap}
70
- .pkg .name{font-weight:600}
71
- .pkg .desc{color:var(--muted);margin-top:2px}
72
- a{color:var(--accent);text-decoration:none} a:hover{text-decoration:underline}
73
- .chip{background:var(--chip);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:12px;color:var(--muted)}
74
- .footer{margin-top:26px;color:var(--muted);font-size:12px}
75
- </style>
76
- </head>
77
- <body>
78
- <main>
79
- <nav class="doc-nav">
80
- <div class="doc-nav-links">
81
- <a href="index.html">Help Home</a><span>&middot;</span><a href="/help/table-of-contents.html">Table of Contents</a><span>&middot;</span><a href="https://github.com/mmiscool/BREP" target="_blank" rel="noopener noreferrer">GitHub</a>
82
- </div>
83
- <div class="nav-search">
84
- <div class="search-input-row">
85
- <input type="search" id="doc-search" class="search-input" placeholder="Search docs... Type at least 2 characters to search" autocomplete="off" spellcheck="false" />
86
- <button type="button" class="search-clear" id="doc-search-clear">Clear</button>
87
- </div>
88
- <div class="search-status" id="doc-search-status"></div>
89
- <div class="search-results" id="doc-search-results" hidden>
90
- <ul id="doc-search-list"></ul>
91
- </div>
92
- </div>
93
- </nav>
94
- <section class="card doc-card">
95
-
96
- <div class="prose">
97
- <h1>History Systems</h1><p>BREP keeps several independent “history” timelines so modeling, PMI, and assembly constraint editing can all replay their steps deterministically. Each history owns the data it needs to recompute results but follows a shared pattern: store a list of entries, redo work by iterating through that list, and serialize just the declarative inputs plus any persistent outputs.</p><h2>Shared building blocks</h2><ul><li><code>ListEntityBase</code> (<code>src/core/entities/ListEntityBase.js</code>) standardizes the three buckets found across every history entry: <code>inputParams</code> (user-editable data), <code>persistentData</code> (saved outputs), and <code>runtimeAttributes</code> (ephemeral session state).</li><li><code>HistoryCollectionBase</code> (<code>src/core/entities/HistoryCollectionBase.js</code>) provides the minimal scaffolding for history managers: it keeps the <code>entries</code> array, an <code>entityClassRegistry</code>, listener bookkeeping, and a monotonically increasing <code>_idCounter</code>. PMI annotations inherit from this base directly, while the other histories implement the same shape manually for flexibility.</li><li>Registries translate user-facing strings into constructors. Modeling relies on <code>FeatureRegistry</code>, PMI uses <code>AnnotationRegistry</code>, and assembly uses <code>AssemblyConstraintRegistry</code>. Every registry exposes tolerant lookups (<code>getSafe</code>) so importing legacy files or plugins does not blow up the run loop.</li><li>Each history exposes <code>toSerializable</code>/<code>toJSON</code> style helpers that deep-clone fields suitable for storage (3MF metadata, <code>.BREP.json</code> dumps, etc.) and matching load/replace helpers so the UI can restore state without bespoke glue.</li></ul><h2>Part History (Modeling)</h2><p><code>PartHistory</code> (<code>src/PartHistory.js:20</code>) is the backbone of modeling mode. It wires together the feature registry, the shared Three.js scene, PMI views, metadata, and the assembly constraint history.</p><ul><li>Constructor duties include initializing <code>features</code>, <code>scene</code>, registries, <code>PMIViewsManager</code>, and a default expression scratchpad (<code>src/PartHistory.js:20-38</code>). The class also injects a soft override on <code>scene.add/remove</code> so debugging geometry churn is possible.</li><li><code>runHistory()</code> iterates over <code>features</code>, lazily instantiates the correct feature class, sanitizes inputs, tracks “dirty” state, runs the feature, and records timestamps plus error metadata (<code>src/PartHistory.js:120-280</code>). Clearing happens up front so each rerun rebuilds the scene from a clean slate.</li><li><code>newFeature()</code> allocates the declarative entry object <code>{ type, inputParams, persistentData }</code>, seeds defaults via <code>extractDefaultValues</code>, and assigns a unique <code>id</code> produced by <code>generateId()</code> (<code>src/PartHistory.js:642-654</code>). <code>sanitizeInputParams()</code> later evaluates expressions, resolves reference selections back into live scene objects, and normalizes booleans, vectors, transforms, or boolean-operation payloads (<code>src/PartHistory.js:665-758</code>).</li><li>Dialog field types, defaults, and selection filters come from each entry’s <code>inputParamsSchema</code>; see <a href="input-params-schema.html">Input Params Schema</a> for every widget option.</li><li><code>toJSON()</code> exports the bare inputs for every feature, expression code, PMI views, metadata, and a snapshot of the assembly constraint history (<code>src/PartHistory.js:426-455</code>). <code>fromJSON()</code> performs the inverse and forwards constraint payloads into <code>AssemblyConstraintHistory.replaceAll()</code> so the solver can pick up where it left off (<code>src/PartHistory.js:457-478</code>).</li><li>File manager save/load uses the JSON helper to embed the complete part history (including PMI and constraints) inside a 3MF metadata entry (<code>src/UI/fileManagerWidget.js:248-320</code>), ensuring a single file captures all three timelines.</li></ul><h2>PMI Annotation History</h2><p>PMI mode keeps its own list of annotations per saved PMI view. <code>AnnotationHistory</code> (<code>src/UI/pmi/AnnotationHistory.js:9</code>) extends <code>HistoryCollectionBase</code> so it inherits the listener set, <code>_idCounter</code>, and serializable entry helpers.</p><ul><li>On construction, it registers every available annotation handler with the underlying registry and remembers the active <code>PMIMode</code> viewer (<code>src/UI/pmi/AnnotationHistory.js:9-20</code>). <code>PMIMode</code> wires this up whenever a PMI view is opened so annotation changes stay scoped to that view (<code>src/UI/pmi/PMIMode.js:66-88</code>).</li><li><code>load()</code> and <code>toSerializable()</code> deep-clone the stored annotation descriptors, strip runtime-only flags (like accordion open state), and keep the declarative input payloads tiny for 3MF persistence (<code>src/UI/pmi/AnnotationHistory.js:23-55</code>).</li><li><code>createAnnotation()</code> looks up the handler via <code>AnnotationRegistry</code>, merges schema defaults with the caller-provided seed, assigns a generated ID, and marks runtime flags like <code>__open</code> or <code>enabled</code> (<code>src/UI/pmi/AnnotationHistory.js:95-129</code>). Because annotations subclass <code>ListEntityBase</code>, they expose a <code>run()</code> method that PMIMode calls when rebuilding temporary geometry, but only their inputs/persistent data get serialized.</li><li>Helpers such as <code>setAnnotationEnabled()</code>, <code>moveUp()/moveDown()</code>, and <code>clear()</code> are thin wrappers around <code>entries</code> mutations that also emit listener events to refresh the PMI widget (<code>src/UI/pmi/AnnotationHistory.js:84-159</code>).</li></ul><h2>Assembly Constraint History</h2><p><code>AssemblyConstraintHistory</code> (<code>src/assemblyConstraints/AssemblyConstraintHistory.js:168-188</code>) mirrors the modeling history but focuses on constraint records and solver execution.</p><ul><li>The constructor receives the owning <code>PartHistory</code> plus a registry and sets up arrays, listeners, and auto-run bookkeeping. Internally it normalizes constraint IDs, links runtime attributes (open state, entity references), and keeps <code>constraintClass</code> pointers on each entry so the solver can call static helpers like <code>applyParams</code>.</li><li><code>snapshot()</code> and <code>replaceAll()</code> provide the serialization surface. <code>snapshot()</code> deep-clones <code>type</code>, <code>inputParams</code>, and <code>persistentData</code> so <code>PartHistory.toJSON()</code> can embed the full constraint set (<code>src/assemblyConstraints/AssemblyConstraintHistory.js:512-546</code>). <code>replaceAll()</code> rebuilds the constraints array from imported JSON, copies schema defaults, rehydrates runtime links, and rebuilds the ID counter before notifying listeners (<code>src/assemblyConstraints/AssemblyConstraintHistory.js:512-620</code>).</li><li><code>generateId()</code> encodes the constraint type into the prefix (e.g., <code>DISTANCE1</code>) so UI rows remain human-readable even after reordering (<code>src/assemblyConstraints/AssemblyConstraintHistory.js:741-748</code>).</li><li><code>runAll()</code> is the high-level solver. It pulls the active <code>PartHistory</code>, crawls the scene to resolve constraint selections, applies translation/rotation corrections with configurable gains, and emits viewer render hooks for debugging (<code>src/assemblyConstraints/AssemblyConstraintHistory.js:749-860</code>). The helper integrates with controller hooks so UI widgets can report progress or cancel iterations.</li><li>Error checking (<code>checkConstraintErrors()</code>), duplicate detection, and auto-run scheduling keep the timeline responsive; listener callbacks keep the assembly constraint widget UI synchronized whenever entries change.</li></ul><h2>Using the histories together</h2><p>Part history is the orchestrator: it owns the Three.js scene and instantiates both PMI and assembly histories. Each PMI view stores a serialized annotation history entry, and every part export bundles feature inputs + annotation descriptors + constraint graph. Because all three systems follow the same entry/persistent-data conventions, plugins or new tools can treat them uniformly—populate <code>inputParams</code>, call the relevant history manager, and rely on the shared JSON helpers to persist the result.</p>
98
- </div>
99
- </section>
100
- </main>
101
- <script>
102
- (() => {
103
- const searchInput = document.getElementById("doc-search");
104
- const resultsBox = document.getElementById("doc-search-results");
105
- const resultsList = document.getElementById("doc-search-list");
106
- const statusEl = document.getElementById("doc-search-status");
107
- const clearBtn = document.getElementById("doc-search-clear");
108
- if (!searchInput || !resultsBox || !resultsList || !statusEl || !clearBtn) return;
109
- const indexUrl = "./search-index.json";
110
- let indexPromise = null;
111
- let cachedEntries = null;
112
-
113
- const escapeHtml = (text = "") =>
114
- text
115
- .replace(/&/g, "&amp;")
116
- .replace(/</g, "&lt;")
117
- .replace(/>/g, "&gt;");
118
-
119
- const escapeRegExp = (text = "") => text.replace(/[.*+?^{}()|[]\$]/g, "\$&");
120
-
121
- const highlight = (text, terms) => {
122
- if (!terms.length) return escapeHtml(text);
123
- const pattern = terms.map(escapeRegExp).join("|");
124
- const regex = new RegExp(`(${pattern})`, "gi");
125
- return escapeHtml(text).replace(regex, "<mark>$1</mark>");
126
- };
127
-
128
- const buildSnippet = (entry, terms) => {
129
- const source = entry.content || entry.summary || "";
130
- if (!source) return { plain: "", highlighted: "" };
131
- let hit = source.length;
132
- for (const term of terms) {
133
- const idx = entry.contentLower.indexOf(term);
134
- if (idx !== -1 && idx < hit) hit = idx;
135
- }
136
- if (!Number.isFinite(hit) || hit === source.length) hit = 0;
137
- const start = Math.max(0, hit - 60);
138
- const end = Math.min(source.length, hit + 140);
139
- const snippet = source.slice(start, end);
140
- return { plain: snippet, highlighted: highlight(snippet, terms) };
141
- };
142
-
143
- const updateStatus = (message) => {
144
- statusEl.textContent = message;
145
- };
146
-
147
- const loadIndex = async () => {
148
- if (cachedEntries) return cachedEntries;
149
- if (!indexPromise) {
150
- indexPromise = fetch(indexUrl, { cache: "no-store" })
151
- .then((res) => {
152
- if (!res.ok) throw new Error("Failed to load search index");
153
- return res.json();
154
- })
155
- .then((json) => {
156
- cachedEntries = Array.isArray(json)
157
- ? json.map((entry) => ({
158
- ...entry,
159
- contentLower: (entry.content || "").toLowerCase(),
160
- titleLower: (entry.title || "").toLowerCase(),
161
- }))
162
- : [];
163
- return cachedEntries;
164
- })
165
- .catch((err) => {
166
- console.error("Search index load failed", err);
167
- cachedEntries = [];
168
- throw err;
169
- });
170
- }
171
- return indexPromise;
172
- };
173
-
174
- const renderResults = (entries, terms) => {
175
- if (!terms.length) {
176
- resultsBox.hidden = true;
177
- return;
178
- }
179
- if (!entries.length) {
180
- resultsList.innerHTML = '<li class="search-item"><div class="search-snippet">No matches found.</div></li>';
181
- resultsBox.hidden = false;
182
- return;
183
- }
184
- const rendered = entries
185
- .slice(0, 30)
186
- .map((entry) => {
187
- const snippet = buildSnippet(entry, terms);
188
- const title = highlight(entry.title || entry.href, terms);
189
- const hasFragment = snippet.plain && snippet.plain.trim().length > 0;
190
- const fragment = hasFragment ? `#:~:text=${encodeURIComponent(snippet.plain.trim())}` : "";
191
- const hrefWithFragment = hasFragment
192
- ? (entry.href.includes("#") ? `${entry.href}&:~:text=${encodeURIComponent(snippet.plain.trim())}` : `${entry.href}${fragment}`)
193
- : entry.href;
194
- const snippetHtml = snippet.highlighted ? `<div class="search-snippet">${snippet.highlighted}</div>` : "";
195
- return `<li class="search-item"><a href="${hrefWithFragment}">${title}</a>${snippetHtml}</li>`;
196
- })
197
- .join("");
198
- resultsList.innerHTML = rendered;
199
- resultsBox.hidden = false;
200
- };
201
-
202
- const performSearch = async () => {
203
- const rawQuery = searchInput.value.trim();
204
- if (rawQuery.length < 2) {
205
- updateStatus("Type at least 2 characters to search.");
206
- resultsBox.hidden = true;
207
- return;
208
- }
209
- const terms = rawQuery.toLowerCase().split(/\s+/).filter(Boolean);
210
- updateStatus("Searching...");
211
- try {
212
- const entries = await loadIndex();
213
- const matches = entries.filter((entry) =>
214
- terms.every((term) => entry.contentLower.includes(term) || entry.titleLower.includes(term))
215
- );
216
- renderResults(matches, terms);
217
- updateStatus(matches.length ? `${matches.length} result${matches.length === 1 ? "" : "s"} for "${rawQuery}"` : "No matches found.");
218
- } catch (err) {
219
- updateStatus("Search unavailable (could not load index).");
220
- }
221
- };
222
-
223
- searchInput.addEventListener("input", performSearch);
224
-
225
- searchInput.addEventListener("focus", () => {
226
- if (!cachedEntries) {
227
- loadIndex().catch(() => updateStatus("Search unavailable (could not load index)."));
228
- }
229
- });
230
-
231
- clearBtn.addEventListener("click", () => {
232
- searchInput.value = "";
233
- resultsBox.hidden = true;
234
- updateStatus("Type at least 2 characters to search.");
235
- searchInput.focus();
236
- });
237
-
238
- document.addEventListener("keydown", (evt) => {
239
- if (evt.key === "Escape" && document.activeElement === searchInput) {
240
- searchInput.value = "";
241
- resultsBox.hidden = true;
242
- updateStatus("Type at least 2 characters to search.");
243
- }
244
- });
245
- })();
246
- </script>
247
- </body>
248
- </html>