1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
//! Small per-tier text/formatting helpers and the `EditorModel.tiers`/
//! `multi_selected_count` push helpers -- the pieces [`super::rows`]'s row builders
//! share, plus the handful other editor callbacks reach directly (multi-select,
//! the tier-list push helpers, the symmetry-preview and meet-name-resolution
//! checks).
use crate::;
use ;
use ;
use ;
/// Splits a tier's [`MeetConstraint`] AND its [`TierTarget`] (if any) into the
/// `(constraint_kind, constraint_text)` pair [`EditorTierItem`] carries and the
/// tier-edit form round-trips through its `LineEdit` -- the inverse of
/// `super::loading::parse_tier_form`'s constraint parsing and
/// `super::loading::parse_tier_target`'s target parsing, combined.
///
/// `target` wins whenever it is `Some`: `Design::resolved_meet_tier_inputs`
/// always leaves `constraint` as a `ScaleReference(0.0)` PLACEHOLDER on a
/// target-bearing tier (`indicatrix_cut_core::design::targets`'s module docs),
/// so reading `constraint` directly would show kind `2` with a meaningless
/// "0" rather than the target the cutter actually authored.
pub
/// The tier table's ANGLE cell text -- always two decimals:
/// a wheel/keyboard nudge accumulates plain `f64` noise (e.g.
/// `-40.300000000000004`), and showing that raw `Display` output read as a
/// cut-off, broken number rather than a rounding artifact. A cutter compares
/// this against a 2-decimal gauge anyway, so truncating the DISPLAY (never the
/// stored value -- the inline editor and the tier-edit form both still read the
/// full, unrounded value) is honest and matches every other angle readout in
/// this app (`view::refresh_design_settings`'s critical-angle text, the margin
/// column, ...).
pub
/// One index-wheel position's display text -- an integer when it lands exactly
/// on a whole tooth (the overwhelming common case), else two decimals. Mirrors
/// [`format_angle_cell`]'s reasoning for the same nudge-noise problem, but
/// integral-aware: `"24"` reads better than `"24.00"` for the ordinary case,
/// while a genuinely fractional index (rare, but real -- see
/// `ManufacturabilityWarning::FractionalIndex`) still shows its non-integral part.
pub
/// Builds one [`IndexChipItem`] per entry in `indices`, flagging exactly the
/// occurrences also present in `detached` -- the inspector's per-facet chip row,
/// pushed by `gui::editor::view::selected_tier_chips` as its
/// own `EditorModel.selected_tier_chips`, NOT a field on [`EditorTierItem`] --
/// that struct's `Vec` is built on a background thread for a large design
/// (`gui::editor::auto_solve`) and shipped to the UI thread inside
/// `BackgroundSolveResult`, which must stay `Send`; a `[IndexChipItem]` field
/// there is `ModelRc`-backed (`Rc`, not `Send`) and does not compile.
///
/// A plain epsilon-equality scan against `detached` (not `crate::orbit::edit`'s
/// private, gear-wraparound-aware `same_index`, unreachable from here) is correct
/// for this: both `Vec`s are the SAME tier's own values, `detached` entries are
/// always pushed from `indices` verbatim (`Design::detach_orbit_member`/
/// `detach_all_in_tier`), never independently authored or wrapped, so no
/// wraparound normalization is ever needed to tell them apart -- only real edits
/// (`facet_toggle_detach`'s Rust handler) need the wraparound-aware comparison,
/// and those already go through core's own helpers.
pub
/// Display text for a tier's `imported_meet` -- what the source `.asc` file's `G`
/// field claimed this facet meets, kept alongside the pinned `constraint` import now
/// writes instead. `""` when there's nothing to adopt, which `EditorView` also uses
/// as its "show the Adopt button" condition.
pub
/// A short label for `units` (`orbit::orbit_units`'s decomposition of one tier's
/// `indices`) plus whether it should be flagged amber. Empty/`false` for a tier with
/// nothing to link (0 or 1 occurrence).
pub
/// Previews a proposed Symmetry Order/Mirror change's effect
/// on every tier's orbit BEFORE `setup_apply_symmetry_callback` actually applies
/// it, mirroring the gear-remap path's own dry-run preview (`gear_remap_preview`),
/// which likewise never mutates `design` to compute its summary. Clones
/// `design.meta` and swaps in only the two fields Apply Symmetry can change --
/// never `design` itself -- then counts a tier the same way its own "orbit"
/// table badge would ([`orbit_status_text`]'s second return value), so "N tiers
/// would become incomplete" always agrees with what those rows will show once
/// applied.
pub
/// One tier's display label for a validation-banner mention -- `"tier 5
/// (Girdle)"` when named, else `"tier 5"`. 1-based to match the tier table's
/// own `#` column, which is what a cutter actually reads off screen.
pub
/// Patches [`EditorTierItem::multi_selected`] onto every row in `rows` from
/// `multi_selected` -- the post-pass a full tier-list rebuild ([`super::rows::
/// tier_items`]/[`super::rows::tier_items_stale`]) needs to survive with the live
/// multi-select highlight intact, since neither builder itself knows about
/// `EditorState::multi_selected` (both always set the flag to `false`; see their
/// own doc comments). Every call site that replaces the WHOLE `editor_tiers` model
/// applies this immediately afterward: `view::refresh_editor_panel`/
/// `push_stale_content`, and `auto_solve`'s background-solve completion.
/// `setup_toggle_multi_select_callback` is the one exception -- it patches the
/// flag onto an ALREADY-pushed model in place instead, using this same function,
/// since toggling a selection changes nothing about `Design` and must never
/// re-run a full tier-list rebuild.
pub
/// Pushes `EditorModel.multi_selected_count` -- the tier table's "N selected"
/// header indicator (`editor_tier_table.slint`) -- kept a SEPARATE call from
/// [`apply_multi_selection`] rather than folded into it, since one of that
/// function's call sites (`auto_solve`'s background-solve worker thread) runs off
/// the UI thread and must never touch a Slint global; every caller of THIS
/// function, by contrast, already runs on the UI thread (the two `view::` refresh
/// paths, the toggle/selection-changed callbacks in `callbacks::tier_actions`, and
/// `auto_solve`'s UI-thread completion handler).
pub
/// Pushes `rows` into `EditorModel.tiers`, reusing the existing model via
/// [`slint::Model::set_row_data`] when the row count is unchanged instead of
/// replacing the whole `ModelRc` -- an ordinary edit (`ModifyTier`), an undo/redo
/// that doesn't change the tier count, or a background-solve completion never
/// resizes the list, and Slint only recreates a `for` loop's per-row component
/// tree when the MODEL ITSELF changes identity, not when one row's data does. A
/// wholesale replacement tears down and rebuilds every row's component tree on every
/// refresh, including one mid-inline-edit -- dropping keyboard focus
/// out of an open inline angle edit (`editor_tier_table.slint`'s `TierAngleCell`)
/// on every refresh, which is exactly what the same-length reuse path above avoids.
/// A structural edit that actually changes the tier count
/// (`AddTier`/`RemoveTier`, or undoing/redoing one) still needs a real replacement
/// -- `set_row_data` cannot resize a model -- so that case still replaces the model
/// wholesale.
///
/// Explicitly invokes `EditorModel.recompute_dirty` afterward rather than relying
/// on `editor.slint`'s own `changed tiers => { recompute_dirty(); }` watcher to
/// catch it: that watcher only fires when the `tiers` PROPERTY itself is
/// reassigned (the `set_tiers` branch below), never when `set_row_data` merely
/// mutates the SAME `ModelRc`'s contents in place -- without this explicit call,
/// the dirty/"Unsaved" indicator would stop updating for the common case (an
/// edit that doesn't change the tier count) the moment that branch is taken.
pub
/// The general form of [`push_tiers`]'s own in-place-update trick (see that
/// function's own doc comment for the full "why" -- rebuilding a Slint `for`
/// loop's whole component tree on every refresh dropped keyboard focus out of an
/// open inline edit): reuses `current` via [`slint::Model::set_row_data`] when its
/// row count already matches `rows`, calling `set` with a fresh `ModelRc` only
/// when the length actually changed (an add/remove, not an ordinary edit).
///
/// `set` is called ONLY on that replace path, never on the reuse path -- exactly
/// matching [`push_tiers`]'s own original behaviour (see its doc comment on why
/// `EditorModel.tiers`'s reassignment is what fires `editor.slint`'s `changed
/// tiers` watcher, and why reusing `current` in place must not also trigger it
/// again for nothing). A caller pushing a property with no such watcher (every
/// other use below) still benefits: skipping the property write when nothing
/// structural changed is itself the point, whether or not Slint's own property
/// setter would already have elided a same-model reassignment.
///
/// `view::push_stale_content`/
/// `push_manufacturability_and_preform_scratch`/`push_selected_tier_chips`
/// (`view.rs`, not this file) route `EditorModel.cutting_rows`/
/// `manufacturability_warnings`/`manufacturability_warning_tiers`/
/// `selected_tier_chips` through this same generic helper instead of each
/// replacing the model with a brand-new `ModelRc<VecModel<_>>` on every single
/// refresh, even a same-length one -- this is what lets each of
/// those call sites share the identical incremental-update behaviour `push_tiers`
/// already had, without four near-duplicate copies of the same length-check.
///
/// Callers still own deciding WHAT to push (the `Vec<T>` computation itself is
/// unchanged); this only changes HOW it reaches `EditorModel`.
pub
/// The first name in `names` (a `MeetConstraint::MeetNamed` constraint's typed
/// list) that does not resolve against `design`'s current tiers -- built from the
/// SAME [`MeetNameResolver`] `indicatrix::geometry::meet_solver::solve` itself
/// uses, so a name that would otherwise silently degrade to a dropped token inside
/// the solver (see that module's own doc comment) is instead caught at Save Tier
/// time with a specific, actionable message. `None` when every name resolves to a
/// real tier or is a recognized meet-point word/connective prose
/// (`TokenResolution::Ignorable`).
pub
/// The design's own representative crown/pavilion facet angles, in degrees
/// (magnitudes), for the crown-window-estimate margin
/// ([`tier_margin_and_risk`]) and the proportion-verdict angle metrics
/// (`view::push_yield_and_proportions`): the tier whose name contains "main"
/// (case-insensitive) on each side, or -- when no tier is named that way --
/// the tier with the largest magnitude on that side. "Crown Main"/"Pavilion
/// Main" is this crate's own template naming convention
/// ([`indicatrix_cut_core::ConstraintTier::standard_round_brilliant`] and every
/// [`indicatrix_cut_core::templates::TEMPLATES`] entry), so this reads the
/// real main facet for every template-derived design and falls back to a
/// reasonable guess for a hand-authored one using different names. `None` on
/// either side when the design has no tier on that side at all.
pub
/// [`EditorTierItem::margin_text`]/`risk_level` for one tier -- pavilion tiers
/// (`tier_angle_deg < 0.0`) read the plain table-only critical-angle margin
/// ([`indicatrix_cut_core::tier_margin_deg`]/[`indicatrix_cut_core::windowing_risk`]);
/// crown tiers (`tier_angle_deg >
/// 0.0`) read the crown-window ESTIMATE ([`indicatrix_cut_core::
/// crown_window_margin_deg`]/[`indicatrix_cut_core::crown_windowing_risk`])
/// against `pavilion_partner_deg`, suffixed `" (est.)"` so it is
/// never mistaken for the same table-only certainty a pavilion row's margin
/// carries -- see that function's own doc comment for exactly what the
/// estimate does and does not model. A girdle tier (`tier_angle_deg == 0.0`,
/// or a crown tier when no pavilion angle could be found at all) always reads
/// `("", -1)`, "nothing to show," not a wrong badge. `n_d` is the design's
/// effective refractive index, the same value the design settings panel's
/// RI/critical-angle readouts show.
pub