Runtime model detail and Preferences ownership
The v1.7.0 G0.2/G0.3 block owns runtime detail policy in
core/model_detail_policy.h. It has no wxWidgets or GDTF dependency and uses
existing GetValue/SetValue preference services and user_config.json storage.
There is no schema migration or GDTF LOD interpretation.
| Config key | Canonical values | Missing/unknown default |
|---|---|---|
viewer3d_model_detail |
low, standard, high |
standard |
viewer3d_moving_proxy |
0, 1 |
1 for Standard/Low; 0 for High; Low always enables it |
These user settings participate in MainWindow preference snapshots/reset and
Core project-load preference preservation. MVR open/merge restores a raw optional
preference snapshot around its project reset, including missing values. Preferences Apply/OK and
EVT_UI_PREFERENCES_APPLIED retain their existing save/refresh behavior.
Deterministic budgets
Triangle counts and all ratios/error limits live in the Core policy:
| Representation | Untouched up to | Target ratios by source triangle count | Target limits | Relative error |
|---|---|---|---|---|
| High | All meshes | 100% | None | No simplification |
| Standard | 2,000 | 65% through 10,000; 50% through 50,000; 40% above | 1,000–100,000 triangles | 0.002 |
| Low | 500 | 60% through 2,000; 35% through 10,000; 20% through 50,000; 10% through 200,000; 5% above | 300–25,000 triangles | 0.01 |
| Optional moving proxy | 500 | Same target ratios and limits as Low, independent of selected detail | 300–25,000 triangles | 0.01 initially |
Budgets floor the ratio result and clamp to the limits and source count. Persistent Standard/Low use meshoptimizer's topology-aware simplification. Budgets are best-effort: constrained geometry may retain more triangles rather than exceed the error limit or drop isolated triangles. Low requests a lower budget and permits a higher error than Standard. Only the temporary proxy keeps the former sloppy-error escalation (0.01/0.02/0.05/0.10/0.20) and deterministic triangle-subsampling fallback. Cache/overdraw optimization remains available for all representations; the overdraw threshold is 1.05.
Representation and cache lifecycle
viewer3d/resources/runtime_mesh_simplification.cpp is the extracted existing
moving-proxy meshoptimizer implementation, parameterized by the Core policy.
It copies only source geometry/material arrays, retaining no GPU handles or
flat/mirrored caches, and simplifies only the runtime copy. High resolves to
the source mesh directly. No generated detail representation is written to a
GDTF or to the existing full-geometry disk cache.
RuntimeMeshCache belongs to one ResourceSyncState and therefore one viewer
and GL context. Its key contains source mesh identity, selected detail policy,
and whether it is a moving proxy. A cache miss queues work and returns the
source immediately. Once source loading finishes, GUI refresh slices build/upload
representations within a 12 ms cooperative deadline and a 128-item cap,
prioritizing moving proxies. Preparation does not
require a full redraw per representation. Camera movement uses prepared
representations without invoking simplification. Generation remains on the
owning GL thread; one exceptionally large mesh is still indivisible.
Changing detail clears generated detail and proxy buffers; toggling only the
proxy clears proxy entries and preserves static detail entries. Unrelated
preferences do not clear this cache. Resolution observes the current preferences
on redraw, so Apply requires no project reload or base-asset reload.
Viewer3DController::ReleaseMeshBuffers invalidates representations for the
source before resource replacement/removal. Existing physical revision and mode
resource keys remain authoritative upstream; callback invalidation prevents
address reuse from retaining stale geometry. Scene replacement cancels queued references before scene destruction. Controller
teardown clears its runtime cache and GPU buffers. The cache is intentionally in memory only.
Fixture instancing continues independently of simplification. All persistent 3D mesh draw paths resolve the same policy, including selection styling. Navigation requests temporary proxies for unselected/unhighlighted fixtures, trusses and SceneObjects; fixture instancing retains its existing batching path. 2D contexts and capture paths bypass detail resolution; picking and bounds keep full geometry. No Layout/PDF policy changes are introduced.
Preferences restructuring
PreferencesDialog is a container/coordinator with a native wxTreebook:
- General: Language, Units, Updates
- Import: Rider Import
- Viewer: 3D Viewer, Selection & Movement
- Formats: GDTF, MVR Import / Export
Category nodes have no page; wxWidgets displays their first child. Pages are
created and loaded once. Navigation never reloads, applies, or saves settings,
so pending edits survive switching. Each cohesive *_preferences_page.* under
gui/preferences/ owns its controls and load/apply wiring. A small
PreferencesPage contract supplies vertical scrolling and responsive wrapping
of explanatory text and long native control labels. Native choices can shrink
with the right pane. No custom rendering/theme or settings backend is added.
The dialog applies every page through the existing preference service, commits
once, and posts the existing unit/preference events only after successful save.
The original dirty-state capture/restore remains in
preferencesdialog_persistence.cpp, using the existing legacy ConfigManager
bridge. Units delegates conversion of pending Rider fields to the Rider page;
Core still owns unit parsing/formatting. Language selection is in its page, while
the restart notice remains in the coordinator and retains its deduplication.
The GDTF page composes the existing mutation-policy and credential panels.
Existing persisted keys/values remain compatible; there is no migration:
| Page | Preserved keys |
|---|---|
| Rider Import | rider_autopatch, rider_layer_mode, rider_lx1_height/pos/margin through rider_lx6_height/pos/margin |
| Units | ui_distance_unit_system, ui_weight_unit_system |
| Language | ui_language |
| Updates | app_update_startup_mode (including the existing never_auto read alias) |
| GDTF | gdtf_mutation_policy; existing credential-vault storage and legacy gdtf_username/gdtf_password synchronization |
| MVR | mvr_truss_geometry_export_mode |
| Selection & Movement | selection_group_move_fixture, selection_group_move_truss, selection_group_move_support, selection_group_move_scene_object, viewport_magnet_show_anchor_references |
| 3D Viewer | viewer3d_invert_orbit_horizontal, viewer3d_invert_orbit, viewer3d_render_style |
The two new detail/proxy keys above live in the 3D Viewer page alongside navigation, render style, and the unchanged shortcut information. The focused rendering panel remains composed inside that page. Selection/MVR helpers accept the existing preference facade as well as ConfigManager, preserving their domain policy and callers. Render-style parsing remains reusable without ConfigManager; its legacy adapter stays in Viewer3D. GPU release/detail resolution live in the adjacent controller implementation; the unreachable duplicate fixture batch was removed.
Intentionally retained coupling: the GDTF credentials panel still uses the existing global GUI services and platform credential vault, and its explicit Validate credentials action still validates/saves credentials immediately. That existing explicit action is independent of unapplied settings edits and is not rolled back by Cancel. Normal page edits persist only through Apply/OK. Credential failures keep their existing warnings/return handling; restructuring does not redesign credential transactions or failed-save rollback. Dirty-state bookkeeping stays behind the existing legacy bridge. Future cleanup can replace that bridge and inject credential services without being required for v1.7.0.
Focused tests cover policy parsing/budgets, immutable source data, uploaded-copy
state, cache reuse/separation/invalidation, real preference store file/buffer
round trips, and wxWidgets panel load/apply for every detail/proxy/render-style
combination. The real-dialog regression additionally covers all eight pages,
existing values, Apply/OK/Cancel, navigation without mutation, pending edits,
unit conversion/events, language notices, and resizing. It isolates OS credential
and locale discovery with test doubles; those backends retain their separate
integration coverage. check_preferences_page_boundary.sh guards page ownership
and the single persistence boundary. check_model_detail_boundary.sh protects ownership and config-key
bookkeeping. Native visual review of demanding/non-manifold assets remains
useful because meshoptimizer budgets are intentionally approximate.
New GUI strings are gettext-ready and extracted into the POT. The complete Spanish and Simplified Chinese catalogs are synchronized and translated, including the category navigation and detail/proxy controls. Strict catalog validation must pass before the Linux CI job runs CTest. Future French/German additions remain outside this block; no new languages are introduced.