brep-io-kernel 1.0.184 → 1.0.186

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.
@@ -0,0 +1,723 @@
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>Expressions and Configurator - 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 :not(pre)>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{margin:0;overflow:auto}
52
+ .prose pre code{display:block;border:0;background:transparent;padding:0;white-space:pre;font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono','Courier New',monospace;font-size:13px;line-height:1.55;color:#eef4ff}
53
+ .prose .doc-codeblock{margin:14px 0;background:#1a2131;border:1px solid #3a4b74;border-radius:12px;overflow:hidden}
54
+ .prose .doc-codeblock-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 14px;background:#2a3247;border-bottom:1px solid #3a4b74}
55
+ .prose .doc-codeblock-lang{font-size:20px;line-height:1;color:#e7efff;text-transform:lowercase}
56
+ .prose .doc-codeblock-body{padding:14px}
57
+ .prose .doc-codeblock-copy{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid transparent;border-radius:8px;background:transparent;color:#9fc5ff;cursor:pointer}
58
+ .prose .doc-codeblock-copy:hover,.prose .doc-codeblock-copy:focus-visible{border-color:#4f6494;background:rgba(159,197,255,0.08);outline:none}
59
+ .prose .doc-codeblock-copy-icon{display:block;width:18px;height:18px}
60
+ .prose .doc-codeblock-copy-icon svg{display:block;width:100%;height:100%}
61
+ .prose .doc-codeblock-copy.is-copied{color:#a8ffd4}
62
+ .prose .doc-codeblock-copy.is-copied .doc-codeblock-copy-icon svg rect{stroke:currentColor}
63
+ .prose .doc-codeblock-js .tok-keyword{color:#d68dff;font-weight:600}
64
+ .prose .doc-codeblock-js .tok-string{color:#7be3a3}
65
+ .prose .doc-codeblock-js .tok-comment{color:#76839f}
66
+ .prose a{color:var(--accent)}
67
+ .prose img{max-width:100%;height:auto;}
68
+ .prose img.doc-gallery-trigger{cursor:zoom-in}
69
+ .prose img.doc-gallery-source-active{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
70
+ .prose table{border-collapse:collapse;width:100%;margin:16px 0;background:var(--panel);border:1px solid var(--border);border-radius:8px;overflow:hidden}
71
+ .prose th,.prose td{padding:8px 12px;text-align:left;border-bottom:1px solid var(--border)}
72
+ .prose th{background:var(--chip);color:var(--text);font-weight:600;font-size:13px}
73
+ .prose tr:last-child td{border-bottom:none}
74
+ .prose tbody tr:hover{background:rgba(92,200,255,0.05)}
75
+ .doc-image-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;padding:24px;z-index:1200}
76
+ .doc-image-modal[hidden]{display:none!important}
77
+ .doc-image-backdrop{position:absolute;inset:0;border:0;margin:0;padding:0;background:rgba(6,10,14,0.86);cursor:pointer}
78
+ .doc-image-stage{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;max-width:min(96vw,1400px);max-height:90vh;width:100%}
79
+ .doc-image-view{max-width:100%;max-height:66vh;width:auto;height:auto;object-fit:contain;display:block;background:#0a1119;border:1px solid var(--border);border-radius:10px;box-shadow:0 16px 40px rgba(0,0,0,0.45)}
80
+ .doc-image-meta{display:flex;align-items:center;justify-content:space-between;gap:10px;width:min(100%,1000px);padding:8px 12px;border:1px solid var(--border);border-radius:10px;background:rgba(12,18,26,0.78);color:var(--muted);font-size:13px}
81
+ .doc-image-caption{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
82
+ .doc-image-counter{white-space:nowrap;color:var(--accent);font-variant-numeric:tabular-nums}
83
+ .doc-image-thumbs{width:min(100%,1000px);padding:8px;border:1px solid var(--border);border-radius:10px;background:rgba(12,18,26,0.78);overflow-x:auto;overflow-y:hidden}
84
+ .doc-image-thumbs-list{display:flex;gap:8px;align-items:center;min-width:max-content}
85
+ .doc-image-thumb{width:94px;height:62px;flex:0 0 auto;padding:0;border:1px solid var(--border);border-radius:8px;background:#0a1119;cursor:pointer;overflow:hidden}
86
+ .doc-image-thumb:hover,.doc-image-thumb:focus-visible{border-color:var(--accent)}
87
+ .doc-image-thumb.is-active{border-color:var(--accent);box-shadow:0 0 0 2px rgba(92,200,255,0.2)}
88
+ .doc-image-thumb-img{display:block;width:100%;height:100%;object-fit:cover;background:#070d14}
89
+ .doc-image-nav,.doc-image-close{position:absolute;z-index:2;width:42px;height:42px;border-radius:999px;border:1px solid var(--border);background:rgba(15,20,27,0.9);color:var(--text);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:24px;line-height:1}
90
+ .doc-image-nav:hover,.doc-image-close:hover{border-color:var(--accent);color:var(--accent)}
91
+ .doc-image-nav:disabled{opacity:0.4;cursor:default}
92
+ .doc-image-prev{left:12px;top:50%;transform:translateY(-50%)}
93
+ .doc-image-next{right:12px;top:50%;transform:translateY(-50%)}
94
+ .doc-image-close{top:12px;right:12px;font-size:28px}
95
+ @media (max-width:720px){
96
+ .doc-image-modal{padding:10px}
97
+ .doc-image-view{max-height:52vh}
98
+ .doc-image-meta{width:100%;font-size:12px}
99
+ .doc-image-thumbs{width:100%;padding:6px}
100
+ .doc-image-thumb{width:76px;height:50px}
101
+ .doc-image-prev{left:4px}
102
+ .doc-image-next{right:4px}
103
+ .doc-image-close{top:4px;right:4px}
104
+ }
105
+ .license{
106
+ background:var(--panel);border:1px solid var(--border);border-radius:14px;
107
+ padding:16px 16px 8px;margin:0 0 18px;
108
+ }
109
+ .license > h2{margin:0 0 10px;font-size:16px;font-weight:700}
110
+ .pkg{
111
+ border-top:1px solid var(--border);padding:10px 0;
112
+ display:grid;grid-template-columns:1fr auto;gap:10px;align-items:center;
113
+ }
114
+ .pkg:first-of-type{border-top:none}
115
+ .pkg .meta{display:flex;gap:10px;flex-wrap:wrap}
116
+ .pkg .name{font-weight:600}
117
+ .pkg .desc{color:var(--muted);margin-top:2px}
118
+ .desc{color:var(--muted);margin-top:6px}
119
+ a{color:var(--accent);text-decoration:none} a:hover{text-decoration:underline}
120
+ .chip{background:var(--chip);border:1px solid var(--border);border-radius:999px;padding:2px 8px;font-size:12px;color:var(--muted)}
121
+ .footer{margin-top:26px;color:var(--muted);font-size:12px}
122
+ </style>
123
+ </head>
124
+ <body>
125
+ <main>
126
+ <nav class="doc-nav">
127
+ <div class="doc-nav-links">
128
+ <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>
129
+ </div>
130
+ <div class="nav-search">
131
+ <div class="search-input-row">
132
+ <input type="search" id="doc-search" class="search-input" placeholder="Search docs... Type at least 2 characters to search" autocomplete="off" spellcheck="false" />
133
+ <button type="button" class="search-clear" id="doc-search-clear">Clear</button>
134
+ </div>
135
+ <div class="search-status" id="doc-search-status"></div>
136
+ <div class="search-results" id="doc-search-results" hidden>
137
+ <ul id="doc-search-list"></ul>
138
+ </div>
139
+ </div>
140
+ </nav>
141
+ <section class="card doc-card">
142
+
143
+ <div class="prose">
144
+ <h1>Expressions and Configurator</h1><p>Use the Expressions panel when you want a model to be driven by a few reusable values instead of typing raw numbers into every feature.</p><p>The panel has two parts:</p><ul><li><code>Expressions</code>: a shared place to define variables and formulas</li><li><code>Configurator</code>: optional UI controls that expose a few important values as sliders, number fields, dropdowns, or text inputs</li></ul><p>This is most useful when you want to build a part once, then resize or retune it quickly.</p><h2>Live Demos</h2><ul><li>Examples hub: <a href="https://BREP.io/apiExamples/index.html">https://BREP.io/apiExamples/index.html</a></li><li>Embeded CAD: <a href="https://BREP.io/apiExamples/Embeded_CAD.html">https://BREP.io/apiExamples/Embeded_CAD.html</a></li></ul><h2>Expressions Panel</h2><p>Write variables in the Expressions box, then reference those variable names in feature dialog inputs.</p><p><img src="expressions-panel.png" alt="Expressions panel" loading="lazy" /></p><p>Example:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
145
+ <div class="doc-codeblock-header">
146
+ <span class="doc-codeblock-lang">javascript</span>
147
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
148
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
149
+ <svg viewBox="0 0 24 24" fill="none">
150
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
151
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
152
+ </svg>
153
+ </span>
154
+ </button>
155
+ </div>
156
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">wall = 2;
157
+ width = 80;
158
+ height = width * 0.6;
159
+ innerWidth = width - wall * 2;</code></pre></div>
160
+ </div><p>Then in a feature dialog you can enter values such as:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
161
+ <div class="doc-codeblock-header">
162
+ <span class="doc-codeblock-lang">javascript</span>
163
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
164
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
165
+ <svg viewBox="0 0 24 24" fill="none">
166
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
167
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
168
+ </svg>
169
+ </span>
170
+ </button>
171
+ </div>
172
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">width</code></pre></div>
173
+ </div><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
174
+ <div class="doc-codeblock-header">
175
+ <span class="doc-codeblock-lang">javascript</span>
176
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
177
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
178
+ <svg viewBox="0 0 24 24" fill="none">
179
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
180
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
181
+ </svg>
182
+ </span>
183
+ </button>
184
+ </div>
185
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">height</code></pre></div>
186
+ </div><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
187
+ <div class="doc-codeblock-header">
188
+ <span class="doc-codeblock-lang">javascript</span>
189
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
190
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
191
+ <svg viewBox="0 0 24 24" fill="none">
192
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
193
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
194
+ </svg>
195
+ </span>
196
+ </button>
197
+ </div>
198
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">width - wall * 2</code></pre></div>
199
+ </div><h2>Quick Start</h2><ol><li>Open the <code>Expressions</code> panel in Modeling Mode.</li><li>Add a few variables in the main text area.</li><li>Click <code>Test Expressions</code>.</li><li>In feature dialogs, enter those variable names instead of fixed numbers.</li><li>Change the variables later to update the model.</li></ol><p>This lets you control repeated dimensions from one place.</p><h2>Configurator</h2><p>The configurator is for values that should be adjusted through UI controls instead of editing the script directly.</p><p>If no configurator widgets exist, the configurator form stays hidden.</p><p>When widgets do exist, the form appears above the Expressions editor.</p><p><img src="configurator-field-types.png" alt="Configurator field types" loading="lazy" /></p><p>Supported widget types:</p><ul><li><code>slider</code>: best for bounded numeric values you want to drag</li><li><code>number</code>: best for precise numeric entry</li><li><code>select</code>: best for choosing from a small list of options</li><li><code>string</code>: best for names, labels, and other text values</li></ul><p>Configurator values are available inside expressions as:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
200
+ <div class="doc-codeblock-header">
201
+ <span class="doc-codeblock-lang">javascript</span>
202
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
203
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
204
+ <svg viewBox="0 0 24 24" fill="none">
205
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
206
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
207
+ </svg>
208
+ </span>
209
+ </button>
210
+ </div>
211
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">configurator.fieldName</code></pre></div>
212
+ </div><p>Example:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
213
+ <div class="doc-codeblock-header">
214
+ <span class="doc-codeblock-lang">javascript</span>
215
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
216
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
217
+ <svg viewBox="0 0 24 24" fill="none">
218
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
219
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
220
+ </svg>
221
+ </span>
222
+ </button>
223
+ </div>
224
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">panelWidth = configurator.panelWidth;
225
+ finish = configurator.finish;
226
+ labelText = configurator.partLabel;</code></pre></div>
227
+ </div><p>You do not need to create an intermediate variable first. In many cases you can enter the configurator value directly in a feature dialog input, for example:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
228
+ <div class="doc-codeblock-header">
229
+ <span class="doc-codeblock-lang">javascript</span>
230
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
231
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
232
+ <svg viewBox="0 0 24 24" fill="none">
233
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
234
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
235
+ </svg>
236
+ </span>
237
+ </button>
238
+ </div>
239
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">configurator.panelWidth</code></pre></div>
240
+ </div><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
241
+ <div class="doc-codeblock-header">
242
+ <span class="doc-codeblock-lang">javascript</span>
243
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
244
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
245
+ <svg viewBox="0 0 24 24" fill="none">
246
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
247
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
248
+ </svg>
249
+ </span>
250
+ </button>
251
+ </div>
252
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">configurator.partLabel</code></pre></div>
253
+ </div><h2>Editing the Configurator</h2><p>Click <code>Edit Configurator</code> below the Expressions area to add or change widgets.</p><p><img src="configurator-editor.png" alt="Configurator editor" loading="lazy" /></p><p>Typical setup:</p><ol><li>Click <code>Edit Configurator</code>.</li><li>Click <code>Add Widget</code>.</li><li>Set the <code>Field Name</code>.</li><li>Choose the widget <code>Type</code>.</li><li>Set the default value and any limits or options.</li><li>Click <code>Save Configurator</code>.</li></ol><p>After saving, the live configurator form appears above the Expressions editor.</p><h2>Naming Rules</h2><p>Each widget needs a field name that can be referenced from expressions.</p><p>Good examples:</p><ul><li><code>panelWidth</code></li><li><code>rib_count</code></li><li><code>partLabel</code></li></ul><p>Avoid spaces and punctuation in field names. Use letters, numbers, <code>_</code>, and <code>$</code>.</p><h2>How Editing Behaves</h2><ul><li>Changing a value in the live configurator form re-runs the model.</li><li>Text and number inputs apply when you press <code>Enter</code> or leave the field.</li><li>Slider drags update live.</li><li>While <code>Edit Configurator</code> is open, the preview above updates as you add or remove widgets.</li><li>That preview does not re-run the model until you save or close the configurator editor.</li></ul><h2>Practical Example</h2><p>Create these configurator fields:</p><ul><li><code>panelWidth</code> as a slider</li><li><code>panelHeight</code> as a number field</li><li><code>finish</code> as a select</li><li><code>label</code> as a string</li></ul><p>Then write expressions like:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
254
+ <div class="doc-codeblock-header">
255
+ <span class="doc-codeblock-lang">javascript</span>
256
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
257
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
258
+ <svg viewBox="0 0 24 24" fill="none">
259
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
260
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
261
+ </svg>
262
+ </span>
263
+ </button>
264
+ </div>
265
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">wall = 2;
266
+ outerWidth = configurator.panelWidth;
267
+ outerHeight = configurator.panelHeight;
268
+ innerWidth = outerWidth - wall * 2;
269
+ innerHeight = outerHeight - wall * 2;
270
+ titleText = configurator.label;</code></pre></div>
271
+ </div><p>Now feature dialogs can use:</p><div class="doc-codeblock doc-codeblock-js" data-code-language="javascript">
272
+ <div class="doc-codeblock-header">
273
+ <span class="doc-codeblock-lang">javascript</span>
274
+ <button type="button" class="doc-codeblock-copy" data-copy-code aria-label="Copy code" title="Copy code">
275
+ <span class="doc-codeblock-copy-icon" aria-hidden="true">
276
+ <svg viewBox="0 0 24 24" fill="none">
277
+ <rect x="9" y="3" width="12" height="16" rx="2" stroke="currentColor" stroke-width="2"></rect>
278
+ <rect x="3" y="9" width="12" height="12" rx="2" stroke="currentColor" stroke-width="2"></rect>
279
+ </svg>
280
+ </span>
281
+ </button>
282
+ </div>
283
+ <div class="doc-codeblock-body"><pre><code class="language-javascript">outerWidth
284
+ innerWidth
285
+ titleText</code></pre></div>
286
+ </div><p>That gives you one place to control the whole part.</p><h2>Where You Can Use Expressions</h2><p>Expressions are mainly intended for feature dialog inputs.</p><p>Common cases:</p><ul><li>numeric fields</li><li>transform values</li><li>vector component fields</li><li>some text fields</li></ul><p>If a feature dialog supports expressions, you can enter either a direct value like <code>20</code> or an expression like <code>width * 0.5</code>.</p><p>You can also enter configurator values directly, such as <code>configurator.panelWidth</code>, without first defining a separate variable in the Expressions editor.</p><h2>Saved With the Part</h2><p>Expressions and configurator values are stored in part history, so they stay with the model through:</p><ul><li>save and load</li><li>JSON export and import</li><li>undo and redo</li><li>embedded history in exported files that preserve feature history</li></ul><h2>Related Docs</h2><ul><li><a href="modes__modeling.html">Modeling Mode</a></li><li><a href="part-history.html">Part History</a></li></ul>
287
+ </div>
288
+ </section>
289
+ </main>
290
+ <script>
291
+ (() => {
292
+ const searchInput = document.getElementById("doc-search");
293
+ const resultsBox = document.getElementById("doc-search-results");
294
+ const resultsList = document.getElementById("doc-search-list");
295
+ const statusEl = document.getElementById("doc-search-status");
296
+ const clearBtn = document.getElementById("doc-search-clear");
297
+ if (!searchInput || !resultsBox || !resultsList || !statusEl || !clearBtn) return;
298
+ const indexUrl = "./search-index.json";
299
+ let indexPromise = null;
300
+ let cachedEntries = null;
301
+
302
+ const escapeHtml = (text = "") =>
303
+ text
304
+ .replace(/&/g, "&amp;")
305
+ .replace(/</g, "&lt;")
306
+ .replace(/>/g, "&gt;");
307
+
308
+ const escapeRegExp = (text = "") => text.replace(/[.*+?^{}()|[\]$]/g, "\$&");
309
+
310
+ const highlight = (text, terms) => {
311
+ if (!terms.length) return escapeHtml(text);
312
+ const pattern = terms.map(escapeRegExp).join("|");
313
+ const regex = new RegExp(`(${pattern})`, "gi");
314
+ return escapeHtml(text).replace(regex, "<mark>$1</mark>");
315
+ };
316
+
317
+ const buildSnippet = (entry, terms) => {
318
+ const source = entry.content || entry.summary || "";
319
+ if (!source) return { plain: "", highlighted: "" };
320
+ let hit = source.length;
321
+ for (const term of terms) {
322
+ const idx = entry.contentLower.indexOf(term);
323
+ if (idx !== -1 && idx < hit) hit = idx;
324
+ }
325
+ if (!Number.isFinite(hit) || hit === source.length) hit = 0;
326
+ const start = Math.max(0, hit - 60);
327
+ const end = Math.min(source.length, hit + 140);
328
+ const snippet = source.slice(start, end);
329
+ return { plain: snippet, highlighted: highlight(snippet, terms) };
330
+ };
331
+
332
+ const updateStatus = (message) => {
333
+ statusEl.textContent = message;
334
+ };
335
+
336
+ const loadIndex = async () => {
337
+ if (cachedEntries) return cachedEntries;
338
+ if (!indexPromise) {
339
+ indexPromise = fetch(indexUrl, { cache: "no-store" })
340
+ .then((res) => {
341
+ if (!res.ok) throw new Error("Failed to load search index");
342
+ return res.json();
343
+ })
344
+ .then((json) => {
345
+ cachedEntries = Array.isArray(json)
346
+ ? json.map((entry) => ({
347
+ ...entry,
348
+ contentLower: (entry.content || "").toLowerCase(),
349
+ titleLower: (entry.title || "").toLowerCase(),
350
+ }))
351
+ : [];
352
+ return cachedEntries;
353
+ })
354
+ .catch((err) => {
355
+ console.error("Search index load failed", err);
356
+ cachedEntries = [];
357
+ throw err;
358
+ });
359
+ }
360
+ return indexPromise;
361
+ };
362
+
363
+ const renderResults = (entries, terms) => {
364
+ if (!terms.length) {
365
+ resultsBox.hidden = true;
366
+ return;
367
+ }
368
+ if (!entries.length) {
369
+ resultsList.innerHTML = '<li class="search-item"><div class="search-snippet">No matches found.</div></li>';
370
+ resultsBox.hidden = false;
371
+ return;
372
+ }
373
+ const rendered = entries
374
+ .slice(0, 30)
375
+ .map((entry) => {
376
+ const snippet = buildSnippet(entry, terms);
377
+ const title = highlight(entry.title || entry.href, terms);
378
+ const hasFragment = snippet.plain && snippet.plain.trim().length > 0;
379
+ const fragment = hasFragment ? `#:~:text=${encodeURIComponent(snippet.plain.trim())}` : "";
380
+ const hrefWithFragment = hasFragment
381
+ ? (entry.href.includes("#") ? `${entry.href}&:~:text=${encodeURIComponent(snippet.plain.trim())}` : `${entry.href}${fragment}`)
382
+ : entry.href;
383
+ const snippetHtml = snippet.highlighted ? `<div class="search-snippet">${snippet.highlighted}</div>` : "";
384
+ return `<li class="search-item"><a href="${hrefWithFragment}">${title}</a>${snippetHtml}</li>`;
385
+ })
386
+ .join("");
387
+ resultsList.innerHTML = rendered;
388
+ resultsBox.hidden = false;
389
+ };
390
+
391
+ const performSearch = async () => {
392
+ const rawQuery = searchInput.value.trim();
393
+ if (rawQuery.length < 2) {
394
+ updateStatus("Type at least 2 characters to search.");
395
+ resultsBox.hidden = true;
396
+ return;
397
+ }
398
+ const terms = rawQuery.toLowerCase().split(/\s+/).filter(Boolean);
399
+ updateStatus("Searching...");
400
+ try {
401
+ const entries = await loadIndex();
402
+ const matches = entries.filter((entry) =>
403
+ terms.every((term) => entry.contentLower.includes(term) || entry.titleLower.includes(term))
404
+ );
405
+ renderResults(matches, terms);
406
+ updateStatus(matches.length ? `${matches.length} result${matches.length === 1 ? "" : "s"} for "${rawQuery}"` : "No matches found.");
407
+ } catch (err) {
408
+ updateStatus("Search unavailable (could not load index).");
409
+ }
410
+ };
411
+
412
+ searchInput.addEventListener("input", performSearch);
413
+
414
+ searchInput.addEventListener("focus", () => {
415
+ if (!cachedEntries) {
416
+ loadIndex().catch(() => updateStatus("Search unavailable (could not load index)."));
417
+ }
418
+ });
419
+
420
+ clearBtn.addEventListener("click", () => {
421
+ searchInput.value = "";
422
+ resultsBox.hidden = true;
423
+ updateStatus("Type at least 2 characters to search.");
424
+ searchInput.focus();
425
+ });
426
+
427
+ document.addEventListener("keydown", (evt) => {
428
+ if (evt.key === "Escape" && document.activeElement === searchInput) {
429
+ searchInput.value = "";
430
+ resultsBox.hidden = true;
431
+ updateStatus("Type at least 2 characters to search.");
432
+ }
433
+ });
434
+
435
+ const initCodeCopyButtons = () => {
436
+ const copyButtons = Array.from(document.querySelectorAll("[data-copy-code]"));
437
+ if (!copyButtons.length) return;
438
+
439
+ const copyText = async (value) => {
440
+ if (navigator.clipboard?.writeText) {
441
+ await navigator.clipboard.writeText(value);
442
+ return true;
443
+ }
444
+ const ta = document.createElement("textarea");
445
+ ta.value = value;
446
+ ta.setAttribute("readonly", "readonly");
447
+ ta.style.position = "fixed";
448
+ ta.style.opacity = "0";
449
+ ta.style.pointerEvents = "none";
450
+ document.body.appendChild(ta);
451
+ ta.select();
452
+ let copied = false;
453
+ try {
454
+ copied = document.execCommand("copy");
455
+ } catch {
456
+ copied = false;
457
+ }
458
+ document.body.removeChild(ta);
459
+ return copied;
460
+ };
461
+
462
+ copyButtons.forEach((button) => {
463
+ button.addEventListener("click", async () => {
464
+ const wrapper = button.closest(".doc-codeblock");
465
+ const codeEl = wrapper?.querySelector("code");
466
+ const text = codeEl?.textContent || "";
467
+ if (!text.trim()) return;
468
+ let didCopy = false;
469
+ try {
470
+ didCopy = await copyText(text);
471
+ } catch {
472
+ didCopy = false;
473
+ }
474
+ if (!didCopy) return;
475
+ button.classList.add("is-copied");
476
+ button.setAttribute("aria-label", "Copied");
477
+ button.setAttribute("title", "Copied");
478
+ window.setTimeout(() => {
479
+ button.classList.remove("is-copied");
480
+ button.setAttribute("aria-label", "Copy code");
481
+ button.setAttribute("title", "Copy code");
482
+ }, 1200);
483
+ });
484
+ });
485
+ };
486
+
487
+ const initImageGallery = () => {
488
+ const proseRoot = document.querySelector(".prose");
489
+ if (!proseRoot) return;
490
+
491
+ const galleryImages = Array.from(proseRoot.querySelectorAll("img"))
492
+ .filter((img) => {
493
+ const src = img.getAttribute("src") || img.src;
494
+ return !!src;
495
+ });
496
+ if (!galleryImages.length) return;
497
+
498
+ const triggerImages = galleryImages.filter((img) => !img.closest("a"));
499
+ if (!triggerImages.length) return;
500
+
501
+ const imageIndexMap = new Map();
502
+ galleryImages.forEach((img, index) => imageIndexMap.set(img, index));
503
+
504
+ const modal = document.createElement("div");
505
+ modal.className = "doc-image-modal";
506
+ modal.setAttribute("role", "dialog");
507
+ modal.setAttribute("aria-modal", "true");
508
+ modal.setAttribute("aria-label", "Image gallery");
509
+ modal.setAttribute("aria-hidden", "true");
510
+ modal.hidden = true;
511
+
512
+ const backdrop = document.createElement("button");
513
+ backdrop.type = "button";
514
+ backdrop.className = "doc-image-backdrop";
515
+ backdrop.setAttribute("aria-label", "Close image gallery");
516
+
517
+ const stage = document.createElement("div");
518
+ stage.className = "doc-image-stage";
519
+
520
+ const closeBtn = document.createElement("button");
521
+ closeBtn.type = "button";
522
+ closeBtn.className = "doc-image-close";
523
+ closeBtn.setAttribute("aria-label", "Close gallery");
524
+ closeBtn.textContent = "x";
525
+
526
+ const prevBtn = document.createElement("button");
527
+ prevBtn.type = "button";
528
+ prevBtn.className = "doc-image-nav doc-image-prev";
529
+ prevBtn.setAttribute("aria-label", "Previous image");
530
+ prevBtn.textContent = "<";
531
+
532
+ const nextBtn = document.createElement("button");
533
+ nextBtn.type = "button";
534
+ nextBtn.className = "doc-image-nav doc-image-next";
535
+ nextBtn.setAttribute("aria-label", "Next image");
536
+ nextBtn.textContent = ">";
537
+
538
+ const viewer = document.createElement("img");
539
+ viewer.className = "doc-image-view";
540
+ viewer.alt = "";
541
+
542
+ const meta = document.createElement("div");
543
+ meta.className = "doc-image-meta";
544
+
545
+ const caption = document.createElement("div");
546
+ caption.className = "doc-image-caption";
547
+
548
+ const counter = document.createElement("div");
549
+ counter.className = "doc-image-counter";
550
+
551
+ const thumbsBar = document.createElement("div");
552
+ thumbsBar.className = "doc-image-thumbs";
553
+
554
+ const thumbsList = document.createElement("div");
555
+ thumbsList.className = "doc-image-thumbs-list";
556
+ thumbsBar.appendChild(thumbsList);
557
+
558
+ meta.append(caption, counter);
559
+ stage.append(closeBtn, prevBtn, nextBtn, viewer, meta, thumbsBar);
560
+ modal.append(backdrop, stage);
561
+ document.body.appendChild(modal);
562
+
563
+ let activeIndex = -1;
564
+ let lastFocusedElement = null;
565
+
566
+ const getImageUrl = (img) => img?.currentSrc || img?.src || img?.getAttribute("src") || "";
567
+
568
+ const thumbButtons = galleryImages.map((sourceImg, index) => {
569
+ const thumbButton = document.createElement("button");
570
+ thumbButton.type = "button";
571
+ thumbButton.className = "doc-image-thumb";
572
+ thumbButton.setAttribute("aria-label", "Show image " + String(index + 1));
573
+
574
+ const thumbImage = document.createElement("img");
575
+ thumbImage.className = "doc-image-thumb-img";
576
+ thumbImage.alt = sourceImg.getAttribute("alt") || ("Image " + String(index + 1));
577
+ thumbImage.loading = "lazy";
578
+ thumbImage.src = getImageUrl(sourceImg);
579
+
580
+ thumbButton.appendChild(thumbImage);
581
+ thumbsList.appendChild(thumbButton);
582
+ return thumbButton;
583
+ });
584
+
585
+ const clearActiveSource = () => {
586
+ galleryImages.forEach((img) => img.classList.remove("doc-gallery-source-active"));
587
+ };
588
+
589
+ const markActiveSource = () => {
590
+ clearActiveSource();
591
+ const source = galleryImages[activeIndex];
592
+ if (source) source.classList.add("doc-gallery-source-active");
593
+ };
594
+
595
+ const scrollSourceIntoView = () => {
596
+ const source = galleryImages[activeIndex];
597
+ if (!source) return;
598
+ source.scrollIntoView({ behavior: "smooth", block: "center", inline: "nearest" });
599
+ };
600
+
601
+ const renderActiveImage = () => {
602
+ const source = galleryImages[activeIndex];
603
+ if (!source) return;
604
+ const sourceUrl = getImageUrl(source);
605
+ const sourceAlt = source.getAttribute("alt") || "";
606
+ viewer.src = sourceUrl;
607
+ viewer.alt = sourceAlt;
608
+ caption.textContent = sourceAlt || "Image";
609
+ counter.textContent = String(activeIndex + 1) + " / " + String(galleryImages.length);
610
+ const canFlip = galleryImages.length > 1;
611
+ prevBtn.disabled = !canFlip;
612
+ nextBtn.disabled = !canFlip;
613
+ thumbButtons.forEach((thumb, index) => {
614
+ const isActive = index === activeIndex;
615
+ thumb.classList.toggle("is-active", isActive);
616
+ if (isActive) {
617
+ thumb.setAttribute("aria-current", "true");
618
+ thumb.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" });
619
+ } else {
620
+ thumb.removeAttribute("aria-current");
621
+ }
622
+ });
623
+ markActiveSource();
624
+ scrollSourceIntoView();
625
+ };
626
+
627
+ const openAtIndex = (index, triggerElement = null) => {
628
+ if (!Number.isInteger(index) || index < 0 || index >= galleryImages.length) return;
629
+ activeIndex = index;
630
+ lastFocusedElement = triggerElement || document.activeElement;
631
+ modal.hidden = false;
632
+ modal.setAttribute("aria-hidden", "false");
633
+ renderActiveImage();
634
+ closeBtn.focus();
635
+ };
636
+
637
+ const closeModal = () => {
638
+ if (activeIndex < 0) return;
639
+ activeIndex = -1;
640
+ modal.hidden = true;
641
+ modal.setAttribute("aria-hidden", "true");
642
+ viewer.removeAttribute("src");
643
+ viewer.alt = "";
644
+ caption.textContent = "";
645
+ counter.textContent = "";
646
+ thumbButtons.forEach((thumb) => {
647
+ thumb.classList.remove("is-active");
648
+ thumb.removeAttribute("aria-current");
649
+ });
650
+ clearActiveSource();
651
+ if (lastFocusedElement && typeof lastFocusedElement.focus === "function") {
652
+ lastFocusedElement.focus();
653
+ }
654
+ };
655
+
656
+ const stepImage = (direction) => {
657
+ if (activeIndex < 0 || galleryImages.length < 2) return;
658
+ const max = galleryImages.length;
659
+ activeIndex = (activeIndex + direction + max) % max;
660
+ renderActiveImage();
661
+ };
662
+
663
+ triggerImages.forEach((img) => {
664
+ const imageIndex = imageIndexMap.get(img);
665
+ if (!Number.isInteger(imageIndex)) return;
666
+ img.classList.add("doc-gallery-trigger");
667
+ if (!img.hasAttribute("tabindex")) img.setAttribute("tabindex", "0");
668
+ if (!img.hasAttribute("role")) img.setAttribute("role", "button");
669
+ img.addEventListener("click", (evt) => {
670
+ evt.preventDefault();
671
+ openAtIndex(imageIndex, img);
672
+ });
673
+ img.addEventListener("keydown", (evt) => {
674
+ if (evt.key !== "Enter" && evt.key !== " ") return;
675
+ evt.preventDefault();
676
+ openAtIndex(imageIndex, img);
677
+ });
678
+ });
679
+
680
+ thumbButtons.forEach((thumb, index) => {
681
+ thumb.addEventListener("click", (evt) => {
682
+ evt.preventDefault();
683
+ if (activeIndex < 0) {
684
+ openAtIndex(index, thumb);
685
+ return;
686
+ }
687
+ if (activeIndex !== index) {
688
+ activeIndex = index;
689
+ renderActiveImage();
690
+ }
691
+ });
692
+ });
693
+
694
+ backdrop.addEventListener("click", closeModal);
695
+ closeBtn.addEventListener("click", closeModal);
696
+ prevBtn.addEventListener("click", () => stepImage(-1));
697
+ nextBtn.addEventListener("click", () => stepImage(1));
698
+
699
+ document.addEventListener("keydown", (evt) => {
700
+ if (activeIndex < 0) return;
701
+ if (evt.key === "Escape") {
702
+ evt.preventDefault();
703
+ closeModal();
704
+ return;
705
+ }
706
+ if (evt.key === "ArrowLeft") {
707
+ evt.preventDefault();
708
+ stepImage(-1);
709
+ return;
710
+ }
711
+ if (evt.key === "ArrowRight") {
712
+ evt.preventDefault();
713
+ stepImage(1);
714
+ }
715
+ });
716
+ };
717
+
718
+ initCodeCopyButtons();
719
+ initImageGallery();
720
+ })();
721
+ </script>
722
+ </body>
723
+ </html>