mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 02:25:55 +00:00
v0.29.0
297 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bba2fc1400 |
Report omitted print-face selections in command-script exports (#10230) (#10235)
* claim: #10230 * fix: report omitted print-face assignments in script exports |
||
|
|
538d7fcb43 |
Language: follow the system language by default (#10138) (#10183)
* claim: #10138 * Follow the system language on a first start (#10138) The language preference defaults to a new 'system' value: the OS locale (sys-locale) is matched against the catalog list - exact tag, base language, then English - and a System choice sits at the top of the Preferences language list. A saved language still wins. * Drop the explicit deref clippy flagged; regenerate THIRD-PARTY-LICENSES.txt for sys-locale --------- Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com> |
||
|
|
34c378c063 |
Save As: rename the design to the new file's name (#4907) (#10036)
* claim: #4907 * Save As: rename the design to the new file's name when its name was borrowed (#4907) --------- Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com> |
||
|
|
1bcc1125f0 |
Graphics: apply the chosen card, or say when it could not (#8341) (#9252)
* claim: #8341 Co-authored-by: Cursor <cursoragent@cursor.com> * Graphics: keep a preferred card when surface marks it incompatible (#8341) Prefer the saved adapter by exact then case-insensitive name, including when only listed as surface-incompatible, and explain in Preferences when another card is still in use. Co-authored-by: Cursor <cursoragent@cursor.com> * Format and clippy Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2f6ca7e46b |
Linux: optional system title bar (server-side decorations) (#9263)
On Linux the window always had no OS decorations and drew Windows-style caption buttons in the application bar. SOLIDCRAFT_TITLEBAR=system, or "titlebar": "system" in graphics.json, now opens the window with the desktop's own title bar and buttons (server-side decorations where the compositor offers them). The default is unchanged; Windows and macOS are unaffected. Claude-Session: https://claude.ai/code/session_01Gnm1n2PVPqghMuR9kHWXfh Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b8eb8d262c |
Settings: saved to disk as soon as they change, not only when the app closes normally (#8275) (#9711)
* claim: #8275 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Settings: written to disk in the frame they change, not only at a clean exit (#8275) eframe saves app.ron every 30 s and when the window closes normally. A change made since the last save was lost whenever the app didn't close normally: ended from the Task Manager, stopped by a sign-out or shutdown, or crashed or hung in the graphics driver on the way out. The next start then showed the old settings (View navigator back to Axes, Display quality back to Normal). The desktop app now writes its settings and flushes them in the frame they change (frames with input and the one after), as the web app already does (#8644). Unchanged settings are not written again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Settings: write at most once a second, and only after input that can change them (#8275) Review: pointer motion came in every frame while the mouse moved, so the settings were serialized every frame; and some settings change in every frame (a panel's width while its edge is dragged), which would have flushed app.ron on the UI thread each frame. Only clicks, keys, text, paste, wheel and pinch count as input now, and the file is written at most once per second: a change made in between waits, a repaint is asked for when its second is over, and it is written then, so an idle app still writes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
edcf02c0d4 |
Crash reports: a window opened after startup no longer labels the run 'during startup' (#8776) (#9661)
* claim: #8776 * Crash reports: a window opened after startup no longer labels the run "during startup" (#8776) egui-wgpu reports "window surface" and "surface configure" for every native window, also the ones opened after startup, such as the agent chat in its own window. Each one overwrote the journal's "running" phase, so a later unclean exit was reported as "during startup (surface configure, N s after launch)". crash::startup_step records a startup step only until the app runs, and the app's egui-wgpu hook calls it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
fc499441e2 |
Lost GPU: the old process exits instead of hanging in the driver, after the settings are saved (#9044) (#9054)
After a lost device the next start began from on_exit, while eframe was still writing app.ron, and the old process then hung in vkDestroySwapchainKHR on NVIDIA. Start it from Drop for App instead, after eframe has joined the settings save and before it drops the window's GPU surface, and end the process there after a loss. The update restart moves there too. The loss reason is passed to the next start and shown in its note. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
32900556e0 |
Control server: a subscribed connection is told of design changes (#8315) (#8333)
* claim: #8315 * fix(control-server): release the watch seat deterministically (#8315) The seat a subscribed connection holds on the DesignWatch lived in the events thread alone, so it left the watch only when that thread ended: on the first write a closed client refuses, which needs the design to move, or when the thread noticed the end of the stream, which waits for the connection's own thread to come out of its blocking read. A closed connection could therefore keep counting on the watch for a second or more, and the test that asks for the count to be gone could lose its race with the ten seconds it waits. The seat is now shared: it leaves the watch once, whichever thread sees the connection's end first, the connection's own thread at the end of the stream or the events thread at the first refused write. The count of subscribed connections is right as soon as the connection is gone, and the events a subscribed connection is told of do not change. |
||
|
|
6c9afc6f7e |
Start the window on OCLP Macs with SYSTEM_VERSION_COMPAT=1 (#3080) (#8930)
On Macs patched with OpenCore Legacy Patcher the OS reports a newer macOS version than the GPU's Metal compiler supports, so wgpu picks a shader standard the compiler refuses (`invalid value 'metal3.2'`) and the window never draws. The variable has to be set before the process starts, and Rust 2024 makes `set_var` unsafe (this crate denies unsafe code), so main() now re-launches the binary with SYSTEM_VERSION_COMPAT=1 when the OCLP NVRAM key (OCLP-Version) is present, and exits with the child's status. A process that already has the variable set never re-launches, so there is no loop and a user's own value is respected. If the check or the spawn fails, startup continues normally. Tested on an OCLP Mac: one re-launch message, graphics render correctly; with SYSTEM_VERSION_COMPAT=1 set beforehand it does not re-launch. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
aff8106205 |
Help: check for updates and show results in the selected language (#8896)
* Help: add manual update checks and status window * Test manual update checks and localized results * Add update check to the native Help menu * Localize Help update checks in all app languages * Document manual update checks from Help * Document ui.help updates control action |
||
|
|
21340ba019 |
Web: Chinese UI loads a Chinese font (#8894) (#8981)
* claim: #8894 Co-authored-by: Cursor <cursoragent@cursor.com> * Web: load Simplified/Traditional Chinese fonts for zh and zh-TW (#8894) BIZ UDPGothic only covers Japanese, so Chinese UI showed tofu. Ship locale-glyph Craft UI SC/TC subsets and fetch the matching face on demand. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
6e39efd4ac |
Theme: System follows the desktop's dark/light setting on Linux GNOME (#7944) (#8123)
* claim: #7944 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Theme: System follows the desktop's dark/light setting on Linux (#7944) On GNOME (Wayland, or X11 through XWayland) the window toolkit never reports the desktop's dark or light preference, nor its changes, so Theme: System kept its colours. The Linux app now reads org.freedesktop.appearance color-scheme through the XDG Desktop Portal's Settings interface (then GNOME's color-scheme and gtk-theme when it has no preference) on a thread of its own, follows the portal's SettingChanged signal, and falls back to gsettings or a slow poll. The window reads the answer through Services::desktop_dark; the decision from the raw values is prefs::desktop_prefers_dark. Windows and macOS keep the window toolkit's theme. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
63923da2ca |
Viewport: an opened design is framed at the home view, in the middle of the view (#9195) (#9204)
* claim: #9195 * Viewport: an opened design is framed at the home view, in the middle of the view (#9195) A fit the viewport cannot frame yet is kept and asked again on the next frame instead of being consumed and lost, so a design opened from the Home screen or by launching on a file no longer comes up at the default camera, wherever its own coordinates put it. Framing then centres the model on the view rather than on the free rectangle between the floating controls: `Layout` carries `middle`, the view without the panels, computed before the compass, the navigation bar and the OK/Cancel corner join the obstacles. The model is still sized to `drawable`, so it stays clear of every control, but only a panel moves it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Viewport: keep the centred fit clear of the controls, and frame --sample (#9195) The fit framed the model into a rectangle the size of `drawable` centred on `middle`, which is not inside `drawable`, so the model could sit under the compass, the navigation bar or the render product frame. The model is now sized to `drawable` as before, and only the room left over in it moves the model towards the middle of the view. `s27_move_triad` turns the Z ring on its far side: the model now sits further right, and the Move dialog that docks on the right afterwards covered the ring's old grab point. A launch with `--sample` builds the design straight into the session and never asked for a fit; it now comes up at the home view, framed, like an opened design. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Viewport tests: the cursor orbit's pivot keeps the pixel it was picked on (#9195) With the model now centred on the view, one of the test's perspective drag points lands within the pick tolerance of the box's top edge, and the pick snaps the pivot onto that edge, a few pixels from the cursor. The orbit keeps that point still, as it should; the test compared it with the cursor instead of with where it started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
92af4a21c8 |
Control channel: a per-request param_check level, applied and confirmed by the app (#9184) (#9185)
* claim: #9184 * Control channel: a request's own param_check level, applied and confirmed by the app (#9184) `engine.execute`, `engine.batch` and `engine.script` take an optional `param_check: "off"|"warn"|"strict"` in their params. The app runs the request at that level through the same `run_checked` path and batch preflight a headless session uses, puts its own level back, and every reply says the level it ran at as `param_check` beside `ok`. A command that switches designs keeps the app's level; any other word is refused. Readings and exports answered on a copy off the window thread keep the level too. `control.capabilities`, answered at once on the control-server thread, lists what the channel does beyond the methods every version has: `{"param_check": ["off", "warn", "strict"]}`. An older app answers "unknown method". The MCP bridge asks it once per connection. When the app lists the level, every call and batch goes with it and the app checks against its own commands; otherwise the bridge checks before sending, as #9073 does. At strict, a reply that doesn't say the app checked the call is an error, and the next calls are checked by the bridge. Also: `add_param_warnings` takes the reply by `&mut`, and `with_param_check` sits beside `Remote::new`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Control channel: a design-switching command is checked at the request's level Review fixes for #9184. `component.edit_in_place`, `component.finish_edit`, `view.next_tab` and `view.previous_tab` ran at the app's own level and echoed `off`, so a misspelt call went through at strict. They now have their parameters checked at the request's level before they run (and still run at the app's own level, as each design keeps its own), and the reply echoes the requested level. The MCP bridge at strict now requires the echo to match its level, not just to be there. `param_check` is read only by `engine.execute`, `engine.batch` and `engine.script`; other methods ignore it. The off-thread test now shows the design change computing off the window thread before it is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4a781b8cd8 |
MCP: --param-check checks calls to the running app (#9072) (#9073)
* claim: #9072 * MCP: --param-check checks calls to the running app (#9072) `mcp --connect` (and `mcp` once it bridges to an app) took `--param-check` and forwarded every call unchecked, while the app's own session is always off. `Remote` now checks each `engine.execute` and `engine.batch` against this server's command contracts before sending: `strict` refuses a call, or a batch with such a step, without sending anything, and `warn` adds `param_warnings` to the app's reply (each step's result in a batch). A command with no contract here goes to the app unchecked. `Auto` carries the headless session's level over when it bridges to a late app. `batch_preflight::contracts` is the contract half of the existing preflight, for a runner in another process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2356a63aea |
CLI: run takes --param-check (#6921) (#6924)
* claim: #6921 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CLI: run refuses unknown options and takes --param-check (#6921) `solidcraft-cli run` now checks its options through `known_args`, so a mistyped option fails instead of being ignored, and takes `--param-check off|warn|strict` with the MCP server's words and default `off`. `warn` prints each unknown key with a suggestion on stderr and runs the step; `strict` stops at that step and writes nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * CLI: run's option check takes --param-check; warn on timed runs as steps complete `run` keeps #6919's (#6918) unknown-option check, with `--param-check` among the options that take a value. `Session::run_script_timed` now takes the same per-step callback as `run_script_each`, so `run --timings` or `--profile` at the warn level reports a step's warnings even when a later step fails, instead of after the script (and not at all on a failure). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9b97e06f71 |
Web: open tabs share settings changes, so one tab no longer undoes another's (#8644) (#8832)
* claim: #8644 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web: open tabs share settings changes, so one tab no longer undoes another's (#8644) Every open tab of the web app saved its whole copy of the settings to the same browser storage, every 30 s and on focus loss, so a tab still holding old settings put them back over a toolbar change made in another tab. A tab now writes its settings in the frame they change, and the other open tabs load them from the browser's storage event, so their own saves keep them. Settings loaded that way aren't written back. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
5f11d92458 |
Opening a design pins its body names, so the first undo evaluates nothing (#8939)
Problem: a design saved before body names were pinned (#6403) is pinned by its first command that changes it, as part of that command. Undoing that command went back to the unpinned design, whose fingerprints no evaluated result matched any more, so the first undo after opening evaluated the whole timeline again: 63-72 s in the SELENE rover (5,484 features) when opened from the command line or headless (`Session::opened_at`), 0.08 s with this change. `doc.open` pinned at the end of the command instead, which kept undo fast but left the design with unsaved changes on opening. Solution: `Session::opened_at` pins the names as it opens the design, with the names the file's bodies have (no result changes, so nothing is evaluated), and takes the pinned design as the saved one: opening is no change. Claude-Session: https://claude.ai/code/session_013WUFq6QFBqmZjxwaKy2J1M Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com> |
||
|
|
3e01081ff9 |
Control token: a closing window removes only its own token and discovery files (#8794) (#8837)
* claim: #8794 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ * Control token: a closing window removes only its own token and discovery files (#8794) Under Flatpak each instance has its own loopback, so a second window binds the same port number as the first and writes the same <port>.token, <port>.pid and current.port in the shared data folder. At exit the first window deleted them all, unconditionally, leaving the second window running with no token file a client could read. remove_token_file now takes this launch's token and removes the token and process files only while the file still holds it; the process id can't tell instances apart, as every Flatpak instance has the same one. remove_current_port also takes the token and keeps the discovery file while another launch's token file holds that port, and removes current.pid with current.port (it was left behind). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019FAWPuFoh9qxn2uS2j53QZ --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
cb05e1e0f7 |
GPU path tracer: run on Metal on macOS (#7183) (#7185)
* claim: #7183 * GPU path tracer runs on Metal on macOS, not only Vulkan (#3217) |
||
|
|
0baf49b25f |
Windows: never load a dxcompiler.dll found on PATH; FXC unless DXC ships beside the app (#6756) (#7923)
* claim: #6756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Windows: never load a dxcompiler.dll found on PATH; FXC unless DXC ships beside the app (#6756) wgpu's Dx12Compiler::Auto loads dxcompiler.dll by name, so Windows takes the first one on PATH. SolidCraft ships none, and DXC 1.8.2505 from other programs' folders (Obsidian, NVIDIA SDK Manager) crashed every start with EXCEPTION_ILLEGAL_INSTRUCTION during the viewport shader compile. solidcraft_gpu::dx12_compiler() now chooses: WGPU_DX12_COMPILER as wgpu reads it when it names a compiler; otherwise DXC from the dxcompiler.dll beside the program, by its full path, else FXC. The window, the headless/CLI instance and the test helpers start with it, and dx12_compiles_with_fxc follows it. WGPU_DX12_COMPILER=dxc keeps the old lookup. Fixes #6756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4571701179 |
Embed a Windows version resource in solidcraft.exe (#8378)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e6dc105134 |
Point clouds: insert PLY, XYZ and PTS scans as a modelling reference (#5151) (#5381)
* Point clouds: insert PLY, XYZ and PTS scans as a modelling reference (#5151) A point cloud is kept in the design as a reference, drawn as depth-tested dots and never a body. - io: a PLY reader (ASCII, binary little/big endian; x, y, z of any numeric type, optional colours, other elements skipped; counts checked against the file) and XYZ/PTS text, thinned evenly past max_points. PLY meshes with faces can also come in through file.insert_mesh. - doc: Document.point_clouds (shared f32 points relative to an f64 origin, colours, units, rigid placement, size and colour), stored as a binary container part; format change in pending/point-clouds.rs. - engine: pointcloud.insert / edit / delete / list; doc.open opens a cloud as a new design; selection.delete takes point_clouds; view bounds include them. - gpu: an instanced point-sprite pipeline (16 bytes a point). - ui: Browser Point Clouds folder with eyes, menu and an Edit Point Cloud card; drop, Open, Insert and file dialogs take .ply/.xyz/.pts; a CPU viewport fallback; messages in en, en-GB, ja, zh, de and ko. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Point clouds: colour by the file, one colour or height, from the UI (#5151) The Edit Point Cloud card gets Color By (File Colors, Single Color, Height). Height is a blue-to-red ramp over design Z, between the cloud's own two heights or, by default, a range worked out from the points (all but the lowest and highest 2 %, from an even sample). pointcloud.insert and pointcloud.edit take color_by and height_range; pointcloud.list reports them. The colour of each point is worked out in one place (PointCloud::shade) for the GPU and CPU viewports. Also: the card's dropdowns compare the picked index (a dropdown's response doesn't report the pick, so File Units did not change either). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Point clouds: integrate format registration and viewport caches (#5151) * Point clouds: satisfy current UI audits and document comparisons (#5151) Route model RGB data through the theme conversion boundary, provide a compact ribbon label, and include point clouds in the new document comparison. Gate the Unix-only CLI-link helper and test so Windows all-target checks remain buildable after the rebase. * Point clouds: isolate translations for current main (#5151) Move the existing 74 messages unchanged into one pending locale file and attribute it through a pending row, following the current repository workflow. Repository checks, 50 tooling tests, final workspace Clippy, wasm and generated-catalog equality checks passed after the rebase. * Point clouds: fit the rebased code to main's GpuTarget and Frame Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Compare point clouds when reusing placement-independent results Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Point clouds: complete command help and required translations (#5151) * Point clouds: size the save cap from the raised limit after the rebase Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com> |
||
|
|
6b523d6839 |
Theme drops: build for the web, and import there too (#8226) (#8534)
DroppedFile::bytes exists only on native; the web build stopped at take_theme_drops. Native keeps reading the file there. On the web, the shell already reads every drop asynchronously before the app sees it: a drop over Preferences' Import tile now arrives as a theme and goes to prefs::import_theme_bytes, instead of being opened as a design. |
||
|
|
d7ef5280c5 |
Ctrl+W closes the design shown, not the browser tab (#5366) (#5466)
Ctrl+W (Cmd+W) closes the active design, asking first when it is unsaved; closing the last one shows the start page. In the web app a capture-phase keydown listener keeps the browser from closing the tab while a design is shown; on the start page the browser closes the tab. Ctrl+W joins the keys the app reserves, and the tab menu's Close shows it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
dd2e44bb12 |
Raise the largest file SolidCraft opens with a setting or launch flag (#4086) (#7565)
* claim: #4086 * Raise the largest file SolidCraft opens with a setting or launch flag (#4086) * Translate the file size limit strings * Size limits: design files are .sc now, .solidcraft still read |
||
|
|
dba3a226f6 |
Closing from the background brings the window forward to ask about unsaved changes (#7783) (#7784)
* Closing from the background: the window comes forward to ask about unsaved changes Right-click › Close on the taskbar while another app had the focus showed the "save changes?" prompt in a window that stayed behind, so the close seemed to do nothing. When the close stops at the prompt, the window now asks for the focus (unminimizing it first) and for the user's attention, which is what native Wayland offers (xdg-activation) and marks the taskbar entry where the system refuses the focus. A window already in focus is left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * X11: ask the window manager to raise the window as a user action winit's Focus asks as an application, which KWin's focus-stealing prevention refuses for a window last used before the switch away: it raised nothing and, since the attention request was then skipped, did not even mark the taskbar. On X11 send _NET_ACTIVE_WINDOW with source 2 (a direct user action) through x11rb, which restores a minimized window too; native Wayland keeps the attention request. Moves the logic into come_forward.rs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Come forward once the taskbar menu is gone, without flicker KWin raised the window, then the taskbar's closing context menu handed the focus back to the previous app 2 to 11 ms later, so the window only came forward some of the time; asking again made it flicker. Ask 120 ms after the close instead, and again only when the focus is taken back within 50 ms of a request (a slow menu) or never came. A later switch away is the user's own and the window stays behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
8be7bb0201 |
Insert design: file names get the dialog's width (#8097) (#8122)
* claim: #8097 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Insert design: file names get the width the size and actions leave (#8097) The web app's "Files in this browser" window listed stored files in an egui Grid sized from cell content: the wide action buttons took most of the fixed window width, the name and size columns were cut, and the rest was empty. Add ui_kit::name_list on the kit's table grid: the name column takes the width the other columns leave, the other columns are as wide as their widest cell (measured as drawn, a changed measurement discards the frame), and a name is cut with an ellipsis and its full text on hover only when it does not fit. The window is a little wider (still capped at the browser width). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e181015fd1 |
MCP save: a folder the agent opened or saved a design in stays in scope (#7181) (#7216)
* claim: #7181 * MCP save: a folder the agent opened or saved a design in stays in scope (#7181) The agent's default folders held the open design's folder only while that design stayed open there, so after a new design, an opened STEP file or a save-as elsewhere a save beside the earlier design was refused, while the same save through the control port worked. The MCP server now keeps, for the rest of its run, the folder of every design file a call opened or saved (open, save, and doc.open, file.save, file.save_as through execute or batch, path given in full) and, headless, of every design open in its session. Such a call only succeeded because the folder was already in scope, so naming a path adds nothing. The folders get the open design folder's terms (inside home or temp, not hidden, hidden entries unreachable), at most 16. Bridged, they go to the app as design_folders, which it judges the same way. The save tool description and docs/mcp.md say how to add a folder with --allow-path. * MCP scope: a design's folder is the folder its file resolves to (#7181) A link in another folder to a design inside the agent's folders passes the scope check, which follows it, but its own folder was what got remembered (and, for the open design, what counted while it was open). Take the folder of the file as the check resolves it. * Control test: a hidden file the export format accepts (#7181) --------- Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> |
||
|
|
03b8f4138b |
New designs save as .sc; .solidcraft files still open (#7325)
* claim: new designs save as .sc Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * New designs save as .sc; .solidcraft files still open and keep their name on Save DESIGN_EXT is now "sc"; DESIGN_EXTS lists sc, solidcraft and solvecraft and every open path (dialogs, drag-drop, recent files, CLI, MCP, links, the web picker) accepts all three. Save of an opened .solidcraft file writes that file; Save As, autosave, recovery and crash reports use .sc. Windows, macOS and Linux register .sc alongside the older extensions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Update UI tests for the .sc default extension Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
7a6c884418 |
Change the app id to io.tangencylabs.solidcraft, with aliases for existing installs (#7681)
The app id moves from ai.storyteller.solidcraft to io.tangencylabs.solidcraft: macOS bundle id (the CLI signs as io.tangencylabs.solidcraft.cli), Linux desktop/metainfo/MIME file names, icon name, Wayland app_id / X11 class, Flatpak, Snap common-id, nfpm/FreeBSD/Nix/AppImage staging, the web app's icon paths and the docs. Existing installs keep working: the Flatpak repository marks both old ids (ai.storyteller.solidcraft and ai.storyteller.solvecraft) end-of-life with a rebase straight to the new id, re-marking the solvecraft ref that pointed at the 0.8 id; the manifest grants read-only access to both old ~/.var/app folders and the first launch copies settings and autosaves from the newest one; AppStream <replaces>/<provides> and X-Flatpak-RenamedFrom list both; the AppImage removes menu entries it wrote under either old id. The data and settings folders (solidcraft / SolidCraft) are unchanged. The MSI UpgradeCode and ProgIDs are unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d2f0627421 |
Follow a theme file and reload it when it changes (#7573) (#7574)
* claim: #7573 * Follow a theme file and reload it when it changes (#7573) * Translate the follow-a-theme-file strings |
||
|
|
0d18757ed7 |
File dialogs open in the folder used last (#7756) (#7807)
* claim: #7756 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * File dialogs open in the folder used last (#7756) The desktop dialogs never said where to open, and the Linux ones then start in the home folder every time. Open, Insert, attach, Save As and Export now open in the folder of the file last chosen in a dialog, or of the open design when another design becomes the open one, whichever came last. Also fixes #7735. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9a7b657fdc |
Assistant: connect the last-used agent on startup (#7009) (#7124)
* claim: #7009 * Assistant: connect the last-used agent and open the panel at start (#7009) Two Settings > AI options, off by default: when SolidCraft starts, connect the agent started last (once the installed agents are known; a missing one is not swapped for another) and open the Assistant panel. Not for runs started with --control. A connection that never gets ready (no agent, start error, the agent ends while starting) is a new Failed state: the header reads Connection failed and Connect reads Retry. The control channel reports it as "failed" and takes connect_at_start / open_at_start. * Assistant: keep the start-up agent when a design replaces the blank one (#7009) Opening a design over the blank design SolidCraft starts with reset its chat and stopped the agent connected at start. A chat not asked anything yet now starts over in place and keeps its agent. Turning AI features off also drops a start-up connection still waiting for discovery. * i18n: Arabic, Hebrew and Vietnamese for the start-up options (#7009) ar, he and vi are complete catalogs on main now, so every new English message needs them. --------- Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> |
||
|
|
2346c1ae81 |
Web app: Report a bug saves its screenshot as a download (#7192) (#7197)
* claim: #7192 * Web app: Report a bug saves its screenshot as a download (#7192) Save screenshot… wrote the PNG with image's save, that is std::fs, which the browser doesn't have. The screenshot is now encoded in memory and written through vfs, as every other save is, so the web app hands it out as a download. Natively the same file is written and ui.screenshot replies as before. A failed save says why in plain text instead of a Rust debug value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web app: a screenshot without a path no longer panics (#7192) `ui.screenshot` without a `path` saved to `std::env::temp_dir()`, which panics on wasm32-unknown-unknown (std has no file system there), and the web app's control requests come through `window.solidcraft.request`. `vfs::temp_dir()` now returns `vfs::BROWSER_TEMP`, a folder of the in-memory files, in the browser, and `save_screenshot` and `vfs::design_folder_refusal` (reached when a request names `allow_paths` while a design is open) use it. Natively nothing changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Web app: keymap and theme exports download (#7192) Both exports wrote with `std::fs::write`, which always fails in the browser. They now write through `vfs::write` like the other saves, so the web app hands the file out as a download. Natively the file is the same; an agent's folder scope now applies to them as to every other write. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Tests: say what the folder-scope checks of the saves show (#7192) The screenshot test wraps the write in a folder scope by hand: that proves the write goes through vfs, not that ui.screenshot's write runs inside the request's scope. ui.screenshot checks the agent's folders when the request arrives; the PNG is written in a later frame, outside that scope, as on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4ad285570c |
Control channel: retire a subscription when the events thread ends (#7226) (#7336)
The seat of a subscribed connection on the DesignWatch lived in the connection's serve thread. It left only when that thread saw the end of the stream, so one unbounded blocking read stood between a closed client and the count. Under load (GPU backend probing and crash-dump children on Windows) that read can sit for seconds; the test's 10 s deadline then expired and `!WATCH.subscribed()` failed (#7226). Move the seat onto the events thread. The thread ends on `stop`, or on the first write a closed client refuses, so the next publish retires the seat. The test now publishes while it waits, and its wait no longer depends on the serve thread. |
||
|
|
ae3d4faf53 |
macOS menu: disable design commands behind an open Report a bug draft (#7188) (#7193)
* claim: #7188 * macOS menu: disable design commands behind an open Report a bug draft (#7188) The native menu bar kept running its actions while a Report a bug draft was open, although the app's own toolbar and shortcuts wait behind it: Edit > Undo or Cmd+Z removed the last feature, so the report described a design that no longer existed. FileAction::enabled now refuses every action but Quit while a draft is open. Settings, Keyboard Shortcuts and Agent Chat are refused too: their windows open over the report but take no input until it closes. The menu's Undo and Redo still go to a focused report field (FileAction::menu_enabled), which no longer leans on Settings being enabled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Menus: Quit and Help's windows wait behind an open Report a bug draft Quit switched to the first design with unsaved changes under the draft and asked about it in a prompt that can't be answered while the report blocks the app; with nothing unsaved it quit and lost the typed report. Like any modal window, the draft now turns Quit off. Help's items that open a window (About, Keyboard Shortcuts, Messages, Crash Reports, Connect AI assistants) wait too, in the native menu and in the app's Help menu; Documentation and Report an Issue… stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6b7f1934b1 |
Startup tests: start the backends the app would, no OpenGL driver on Windows (#1156) (#7217)
* claim: #1156 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Startup tests: start the backends the app would, no OpenGL driver on Windows (#1156) The software-adapter test enumerated every wgpu backend and the probe test asked OpenGL on its own even where the app stops at D3D12, so both loaded the GPU vendor's OpenGL driver (atio6axx.dll on AMD) into the test process, where it once aborted the process at exit with c0000409. The adapter test now starts what the software renderer starts (D3D12 alone on Windows) and the probe test probes in the app's order, stopping at the first backend with a GPU. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Startup tests: read the software renderer's backends from wgpu_options Review: the adapter test now takes the backends from the app's own wgpu_options(true, None) instead of a copy of its choice, and the probe test no longer re-checks the order choose_backends' own test covers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
9d01dcb358 |
Open from computer: let iOS and iPadOS pick .solidcraft files (#6977) (#7351)
* claim: #6977 * Open from computer: let iOS and iPadOS pick .solidcraft files (#6977) The iOS file picker matches an input's accept list by system file type, not by extension. No system type has the .solidcraft extension, so the designs were greyed out. On an iPhone or iPad the file inputs now offer every file; other devices keep the extension filter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b47f82d406 |
Reopen the designs open at the last quit (#5542) (#6006)
* claim: #5542 * Reopen the designs that were open at the last quit (#5542) A launch with no file to open reopens the saved designs that were open when SolidCraft was last closed, in tab order. Designs never saved and files that are gone are skipped. Preferences > General > Reopen designs turns it off; a launch driven over --control or with --sample starts fresh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Move the new messages to a pending locale file Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com> |
||
|
|
a7c1909766 |
CLI: evaluate scripts on the input design (#6861) (#6872)
* claim: #6861 eval scripts on input designs * fix(cli): evaluate scripts on the input design (#6861) * fix(cli): validate eval options before opening inputs Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
3726959d82 |
Control channel: check the token before parsing the whole request (#6643)
Until a connection had shown the token, its first line (up to 16 MiB) was parsed whole into a JSON value. A line of [0,0,...] that size is hundreds of MB once parsed, so 16 connections without a token could make the app run out of memory and lose unsaved work. The token is now read from the request object without building the rest of it: other fields are only skipped, and anything but an object with a string token is refused as before. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ebb6cc53ab |
Crash reports: capture native crashes on macOS (#2113) (#6593)
* claim: #2113 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Crash reports: capture native crashes on macOS (#2113) A signal handler for SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP and SIGABRT writes the same <journal>.native.json as the Windows exception filter: the signal, the faulting module and offset, the thread and a frame-pointer walk. The next start reports it instead of "an unclean exit". The signal then goes on to the previous handler or the default action, so macOS still writes its own crash report. native_crash_macos.rs joins the crash-safety skill's list of files that may use unsafe (no safe API can catch these signals). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
afd6a5c584 |
Send to Slicer: send the selected bodies to a slicer (#2595) (#3600)
* Add Open in Slicer for the selected bodies (#2595) One command saves the selected bodies as a 3MF in millimetres (an STL if the 3MF can not be written) and opens it in the last slicer used. It is on the File menu, the Mesh tab and the body right-click menu. The first time, or when that slicer is gone, it asks which one with the same list Save as mesh uses. The Save as mesh dialog is unchanged. * Send slicer files to a private per-user folder (#2595) Files sent to a slicer used to go to a shared folder in the temp directory. They now go to the user cache folder (mode 0700 on Unix), under a name that stays the same for the same design and bodies, so sending again replaces the file and the slicer can reload it. The file is written to a temporary name and renamed into place. If the name can not be replaced, as on Windows while the slicer holds the file open, the copy gets a number and the status line says so. Files older than seven days are deleted at startup. The first send tells the user that sending again replaces the open file, and so does the tooltip of Open in Slicer. Send to slicer in Save as mesh uses the same path. * Open files in a running slicer (#2595) PrusaSlicer, SuperSlicer, Bambu Studio and Cura are started with --single-instance, so a slicer that is already open loads the file instead of opening a second window. OrcaSlicer has no such flag and is left alone, and macOS bundles are single-instance already. PrusaSlicer 3.0 ships a launcher entry that already carries --single-instance-on-url; it keeps that one and never gets a second flag. It is a preference, on by default, and scripts can pass single_instance. Preferences also gets a button to forget the slicer Open in Slicer remembers. * List slicers the desktop entries say open 3MF or STL (#2595) On Linux the scan of desktop entries now also lists a program that claims 3MF or STL in MimeType, even when it is not in our table of known slicers. The table still supplies names, ids, icons and flags for the ones it knows. Entries marked NoDisplay or Hidden are skipped, and so are terminal programs and SolidCraft itself. The existing size and count limits stay. The Windows scan now looks inside a Prusa3D folder, where PrusaSlicer installs itself. * Only start slicers the user can see or chose (#2595) file.save_mesh took a slicer parameter that could be any executable path, so a script or an MCP client could run any program. The slicer now has to be one of the detected slicers (by id or name) or a program the user already added under Custom, which the app hands to the engine from its settings. The same rule holds for file.open_in_slicer. The agent chat already asked before any file command; its question now also names the slicer. * Review fixes for Open in Slicer (#2595) The file name hash keeps all 64 bits instead of 32. When Open in Slicer has to fall back to an STL, the status line now says so. The engine test that sets the chosen programs clears them again. The unused "could not make the folder" message is gone from the catalogs, and the docs say plainly that finding slicers by file type is Linux only. * Keep unsaved designs apart and hide Open in Slicer in the browser (#2595) Two unsaved designs with the same name and body names got the same file in the slicer folder. Every document now has a uid that is new for each New, Open and Recover and stays the same for the life of the document, and the file name uses it (with the process id) until the design has a path. At the first Save As the name changes to the path-based one, so the slicer gets one new file; the old one goes with the seven-day sweep. The browser can not start a program, so Open in Slicer is now disabled there: the engine command reports why, the File menu item and the body and component right-click items are greyed, and starting it anyway says so on the status line instead of opening a dialog. The Save as mesh dialog keeps its browser guard for the open flow too. * Put the Session doc comment back on Session (#2595) The new NEXT_UID static had landed between the doc comment and the struct it describes. * List every install of a slicer, and test the first Save As (#2595) Detection kept one entry per known slicer, so a system PrusaSlicer 2.9 and a Flatpak 3.0 beta showed up as one. Every distinct install is listed now. The same program found twice, through a desktop entry and through PATH, is still one. When a slicer has several installs, each gets an id made from where it was found (prusa#comprusa3dprusaslicer) that stays the same on every run, and a name worked out from its command and location without running anything: PrusaSlicer (system), PrusaSlicer (Flatpak, beta), PrusaSlicer (AppImage). A lone install keeps its plain id and name. The plain id or name still picks the first install, so a remembered "prusa" keeps working. Single-instance flags and the other per-slicer behaviour go by the id without the install part. Also a test that the first Save As changes the file name once and later sends keep the name that comes from the saved path. * Start PrusaSlicer 3.0 first and send the file once it is ready (#2595) PrusaSlicer 3.0 starts through slic3r-app-launcher, which ignores a file given at a cold start whatever the flag, and loads it over D-Bus when an instance is already running. A small table in slicers.rs names such slicers by their command; today it holds that launcher, so PrusaSlicer 2.9 and OrcaSlicer are untouched. Ready means a name starting with com.prusa3d.prusaslicer.BroadcastListener. is on the session bus, read from busctl --user list with no new dependency. If busctl is missing or fails, nobody waits and the file is sent as before. When the slicer is not ready, Open in Slicer starts it without the file and looks every 200 ms for up to 10 seconds. The wait runs on another thread, so the window stays live, and the status line says Starting PrusaSlicer. Once the name appears the file is sent with the same arguments as before. If the limit passes, the status line says the slicer did not respond, that the file is already saved, and to send again. A second click during the wait only repeats the Starting line, so there is one launch and one send. Scripts calling file.open_in_slicer wait inside the command by default, or pass wait: return or no. * Always show the slicer picker for Open in Slicer (#2595) File > Open in Slicer, the Mesh tab entry and the right-click entry now always open the picker, with the slicer used last time pre-selected, so the usual case is one click or Enter. Scripts and file.open_in_slicer with a slicer still send without a dialog. The preference button that forgot the remembered slicer is gone: it could no longer make anything ask again, and it did not clear the pre-selection either. The guide says how it works now. * Leave Bambu Studio out of the single-instance list (#2595) Open files in a running slicer now adds --single-instance for PrusaSlicer, SuperSlicer and Cura only. Bambu Studio is started as it is, so it may open a second window. A test pins that, for the detected slicer and for one the user picked as a custom program. The guide and the preference tooltip no longer name Bambu Studio as getting the flag. * Sort the English catalog and leave features.md to the generator (#2595) cargo xtask lists wanted one message moved in en.json after the rebase. docs/features.md is written by the Generated files workflow on main, so the row I had added by hand is gone from this branch; the workflow adds Open in Slicer when it lands. * Drop the unused folder message from the Vietnamese catalog (#2595) Vietnamese arrived on main after I removed ui.save_mesh_temp_failed from the other catalogs, so the check that every translated id exists in English failed on it. Nothing uses the message any more. * Say so when a started slicer can not be found again (#2595) After Open in Slicer had started a slicer on its own, a failure to look it up again to send the file just returned, so the status line stayed on Starting and then nothing. It now says the slicer was started, that it can not be found to send the file, and where the file is saved. A test covers it. * Say why a failed busctl ends the wait (#2595) If busctl fails partway through a wait, the wait stops and the file is sent, the same as when busctl can not be run at all. That is on purpose: a file sent too early is dropped quietly and costs one more send. A comment in wait_for_ready says so. * Keep the readiness wait inside its limit (#2595) wait_until asked the probe before checking the deadline, including right after a sleep, so a yes could be accepted after the limit, and busctl ran with no timeout, so one look could outlast the whole wait. The deadline is checked first now, the probe is told how much time is left, and busctl is given at most two seconds (less near the end of a wait) and is killed after that. A hung busctl counts as not telling, which sends the file as before. This covers the first look on the UI thread too. Tests cover a slow probe, the shrinking time left, and a fake busctl that hangs, answers and fails. * Send the file that was already written once the slicer is ready (#2595) When the wait for a started slicer ended, the UI ran file.open_in_slicer a second time. That exported the design again on the UI thread, from whatever the session looked like by then, and could write a numbered copy while the timeout message still named the first file. The wait now keeps the slicer it found and the answer of the first call, and opens the file that was written. A test changes the design during the wait and shows the same file is sent, is not written again, no command runs, and no second file appears. * Send with the single-instance flag after starting a slicer (#2595) After Open in Slicer has started a launcher that needs a start first, the file has to reach the instance that is now running, so the send that follows a start carries --single-instance whatever the preference says. A normal send, to a slicer that was already up, still follows the preference. A comment in both places says why. The test uses a plain launcher command with the preference off: the normal send has no flag and the send after a start has it. * Find the remembered slicer again after an uninstall (#2595) With two installs the remembered key is prusa#prusaslicer. Remove the other install and the lone one is plain prusa, which matched neither the exact key nor the plain-key fallback, so the picker fell back to the first slicer in the list, maybe a different one. The key is now also matched by the part before the #, so the same slicer is pre-selected. A test covers the lone install, a plain key with two installs, an exact key, and that another slicer is never picked. * Say a second click did not send anything while a slicer starts (#2595) While a started slicer is being waited for, another click showed Starting <the first slicer> even when it had picked another slicer or other bodies. It now says it is still waiting for that slicer and that nothing else was sent. The test checks the wording and that it names the slicer being waited for. * Label macOS installs by their application (#2595) An open -a command resolved its program to /usr/bin/open, so every application on a Mac was labelled system. Labels for open commands now come from the .app path: /Applications is system and a folder under home is shown as ~/Applications. A test with two PrusaSlicer.app bundles and a made-up home checks both labels and that the ids differ. * Pick the same place for a program found twice every time (#2595) When two desktop files, or a desktop file and PATH, pointed at one program, the first one read was kept and its file name went into the id, so the ids could change with the order the folders were read in even though nothing was installed or removed. The place that sorts first now speaks for the program. A test with two aliases and a second install lists them with the folders in both orders and gets the same ids and commands. * Treat flatpak run spelled two ways as one install (#2595) flatpak run ... and /path/to/flatpak run ... with the same arguments had different install keys, so one invocation could be listed twice. The launcher is now compared by its file name and the arguments are kept, so both give the same key, while a different branch is still its own install. The test uses a real launcher file so the full-path entry is not skipped as stale. * Say plainly that a late yes is accepted on purpose (#2595) The comment on wait_until said a slow probe could not accept a yes that came too late. That was not what the code does: no probe is started after the limit, but one already running may finish and its yes is taken, because the file should still go. The comment now says so, with the bound: the limit plus that last probe, which in production is given the time left and so ends by the limit. A test has a probe that runs past the limit and says yes, and checks it is accepted and the wait stays within limit plus the probe time, and that a probe that says no is not followed by another. * Give reading busctl the same deadline as running it (#2595) After busctl exited, the output was read with a join that could wait forever if a process it left behind kept the pipe open. The reader now hands the text back over a channel and the wait uses the time that is left of the same limit, and gives up with nothing otherwise. The polling sleep no longer overshoots the time left either. A test uses a fake busctl that backgrounds a sleep with its output inherited and exits at once: the call returns within its limit. * Tell two different flatpak launchers apart (#2595) Comparing only the file name of the launcher made /opt/tool-a/flatpak run ... and /usr/bin/flatpak run ... with the same arguments one install even when they are different programs. The launcher is now found the way any program is, through PATH and with links followed, and its name stands in only where it can not be found. The test has flatpak on PATH and the same file by its path as one install, a different executable of the same name as a second, a different branch as a third, and the fallback when there is no PATH. * Keep waiting when busctl is slow, send only when it is missing or fails (#2595) Since the busctl timeout, a slow busctl came back as "can not tell", and that was read as send now. One slow look, likely just when a cold PrusaSlicer 3.0 is starting under load, ended the wait and sent the file early, which gets dropped. The look now says why it has nothing: slow means not up yet, so the wait goes on to its limit and then reports that the slicer did not respond; a missing or failing busctl still means it can not be told and the file is sent. The comments name only those two cases. Tests use fake busctl programs: one that hangs keeps the wait going to the limit and times out, a missing one sends at once, one that says not up and then fails sends, and one that names the slicer is ready. * Do not open an empty path when the answer had no file (#2595) If the answer kept for a started slicer had no path, the send ran open with an empty string. The tick now says there is no file to send and starts nothing. The test gives a wait with no path, checks the error line, and that the fake slicer was never run. * Drop the unused folder message from the Danish catalog (#2595) Danish arrived on main with ui.save_mesh_temp_failed, which this branch removed from English because nothing uses it any more, so the check that every translated id exists in English failed. The entry is gone from the Danish catalog too. * Always start the 3.0 launcher with the single-instance flag (#2595) A hand run showed that starting the PrusaSlicer 3.0 launcher with no file while a 3.0 is already running forwards to it and exits when the flag is there, but opens a second full window when it is not. A slow first look at the bus can start the launcher twice, and with the preference off the flag was stripped. A slicer that has to be started first now keeps the flag on that start whatever the preference says, the same reasoning as the send after a start; when nothing runs the flag does no harm. A normal send with the preference off still has none. The test covers the launcher entry, a bare launcher command, a normal send and a slicer that is not on the table. * Swap --single-instance-on-url where it does not hand the file over (#2595) A hand run on the PrusaSlicer 2.9.6 Flatpak showed that its entry's --single-instance-on-url does not forward a file to the running instance: every send opened a new one, three after a cold start and two sends. With plain --single-instance the second process finds the first, forwards, and exits. So when a command carries --single-instance-on-url and the slicer is not on the wait table, that is, not the 3.0 launcher, it is replaced by --single-instance, with the file markers handled as before, when the preference is on, and stripped when it is off. On the 3.0 launcher the flag does forward and stays. The comment on the rule says why. A test uses the real 2.9.6 and 3.0 Flatpak command lines. * Cut long slicer names to fit the picker (#2595) With installs listed on their own, a name like PrusaSlicer (Flatpak, master) is wider than its row. The list painted it with no limit, so it ran out of the popup, and the closed selector wrapped it onto a second line. The list now lays the name out on one line that ends in an ellipsis when it does not fit, the selector truncates, and the full name is in the tooltip when it was cut (for a program the user added, ahead of the delete hint). A test checks that a long name is cut to one line within the width and a short one is left alone. * Keep the 3.0 launcher's flag on a send whatever the preference says (#2595) A hand run with the single-instance preference off showed the 3.0 launcher needs its flag on a send too, not only on a start: a cold start with a file and no flag loaded nothing, and a send to a running 3.0 left a launcher process that never forwarded. A slicer on the wait table now gets the flag on every launch, as it already did when started alone. The doc comment says why. The preference text, in English, Japanese and Chinese, and the guide now say that PrusaSlicer 3.0 is the exception to turning this off. The tests that took the flag away with the preference off now expect it, for the launcher entry, a bare launcher command and the 3.0 Flatpak line; the test of the send in the UI checks the first send carries it. * Keep the install-flag swap to PrusaSlicer, found by what it is (#2595) The swap of --single-instance-on-url for --single-instance ran for any program whose command had the flag, so another program with that flag would have been rewritten. It is now a PrusaSlicer rule: it applies to a slicer recognised as PrusaSlicer by its identity, its id or, for a program the user picked, the name it goes by, never by a version number. The single-instance table uses the same identity check. A test keeps the flag of a program that is not PrusaSlicer, strips it when the preference is off, and swaps it for a picked program named PrusaSlicer. * Do not promise a hard bound in the wait comment (#2595) The comment on wait_until said the production probe ends by the limit. Its clock starts after the process is spawned, and killing and reaping the process and scheduling are not bounded by it, so it is a polling and read budget with process overhead on top. The comment says that. Comment only. * Tell PrusaSlicer 2 from 3 by the installed version (#2595) PrusaSlicer 2 and 3 need different handling, and the rules had to guess from the command line. Each install of PrusaSlicer now carries its major version, read when it is detected from the release at the top of its appstream metadata and never by running the program: a Flatpak's own files (the branch the command names, else the current one), or the metainfo file beside a desktop entry. PrusaSlicer older than 3 is sent the file with --single-instance in place of --single-instance-on-url, which does not hand a file over there, and is not waited for. PrusaSlicer 3 and newer keeps the launcher's flag, is started on its own, waited for, and sent the file. When the version can not be read, the command decides as before: the slic3r-app-launcher launcher counts as 3, and any other entry is left as it is. A program that is not PrusaSlicer is untouched whatever its flags. Tests use metadata fixtures, no Flatpak: 2.9.6, 3.0.0-alpha10, a branch that is not installed, a system package, an unreadable version with and without the launcher, and a non-PrusaSlicer program with the same flag. * Make the busctl wait tests steady under load (#2595) One of them failed once in a full run at its first assertion and passed alone. A script that has only just been written can fail to start (text file busy) while another test forks, and that reads as a busctl that is missing. The fake programs are now started once, patiently, with a flag they answer at once, before a test relies on them; a hanging one uses exec so it is killed cleanly. The time bounds that could trip on a busy machine are several seconds now, and the checks are on outcomes: not ready and keeps waiting, send now, and the number of looks. Thirty runs alone, thirty of the whole slicers filter and thirty of the busctl tests under eight busy loops all pass. * Say what the busctl reader thread leaves behind (#2595) The thread that reads busctl output is left to end when the pipe closes. A busctl that answered but left a process holding its stdout reads as slow, and every poll of such a bus can leave one blocked reader behind until that process lets go. A comment next to the thread says so. Comment only. * Make Save as mesh a plain export (#2595) Save as mesh asked for the bodies and the format, and could also send the file to a slicer with a checkbox and a slicer list. Sending now has its own command, so the checkbox, the list and the Send label are gone from this dialog: bodies, format, a file dialog, Save. The two settings that only the checkbox used, save_mesh_send and save_mesh_slicer, are no longer kept; a settings file that still has them loads, and they are ignored. The strings only they used are gone from the catalogs. The slicer list stays in the dialog of Open in Slicer. file.save_mesh keeps its slicer parameter for scripts. The guide describes the two as separate. * Rename Open in Slicer to Send to Slicer (#2595) The command sends a file to a slicer rather than opening one, and a later Send to App can share the name. File menu, Mesh tab, right-click menus, the dialog button, status lines, the guide and the English, Japanese and Chinese strings now say Send to Slicer, and the command id file.open_in_slicer is file.send_to_slicer. It is new in this change, so there is no alias. The remembered slicer is stored as send_slicer, and the question the agent chat asks says send to. Module and type names stay as they are. * Put the send targets in one table and one send pipeline (#2595) The known slicers, the list of slicers with a single-instance flag and the rule for slicers that must be started and waited for were three separate things. They are one table of targets now: id, label, kind, names and program files, accepted formats and the default, how the file is handed over, the single-instance flag, the version from which PrusaSlicer's on-url flag hands a file over, and the wait-for-ready rule. Programs that are not in the table are slicers with the same defaults. The kind is slicer, the only one so far; its comment says it is where a later Send to App would plug in. The send itself is one function: write the bodies, find the target, start it and wait if its rule says so, hand the file over, report. Detection and resolution take a kind. Nothing changes for what already worked. file.save_mesh keeps its slicer parameter, now through the same function, with its export arguments and result unchanged: a cold PrusaSlicer 3 is started and waited for before the file goes, and it accepts the program path of a detected slicer as file.slicers lists it (file.slicers gives it as program). A path is accepted only when it is the program of exactly one detected target, and that target's own command and arguments run; a launcher several entries share, such as /usr/bin/flatpak, is refused as ambiguous, and any path nobody listed or chose is refused. The parameter and the code say it may be deprecated later in favour of file.send_to_slicer or a Send to App workflow, which is a possibility and not a plan. Tests go through file.save_mesh itself for the two reported failures, a listed AppImage path and a cold PrusaSlicer 3 that is waited for, with the slicers and the busctl made up; and they check that the result and the file are what an export without a slicer gives, and that a path that is not listed, or is shared by two entries, writes nothing. The pipeline has its own tests: a cold launcher waited for, one already up, wait return, and one that never comes up. A test of save_mesh that shared a file name with another is given its own. * Keep an AppImage's id through an upgrade (#2595) When a slicer has several installs, each gets an id made from where it was found. For an AppImage that was its file name, which carries the version, so upgrading it changed the id and the remembered choice missed. The id is now made of the slicer's id, the fact that it is an AppImage and a short hash of its folder, so a new version that replaces the old one in the same folder keeps the id, and the same AppImage in another folder is its own install. Two versions kept side by side in one folder are two installs: while both are there, their file names are added to their ids to tell them apart. A remembered id is found again only when it names exactly one install. An id that was one AppImage and is now two versions, or a plain id that is now several installs, is treated as not remembered, so the picker starts from the first entry as on a first run and does not quietly pick another install; a single install left of the same slicer is still found, as before. Installs that are not AppImages are told apart as before. An id from before this change no longer matches. Tests cover an upgrade, two versions side by side, a second folder, and the picker's side of each case. * Add Open with EasyPrint to Send to Slicer EasyPrint is Printables' slicer in the browser. It's a plain text row in the Send to Slicer dialog, backed by a new file.open_in_easyprint command. The command exports the bodies as a 3MF (or STL), hands them to the host to upload to slicer.printables.com/import, and the app opens the link that comes back. The desktop app uploads with ureq and the browser build with fetch, so this is the one send target that works on the web. The model leaves the machine, so the command refuses without consent: true, the dialog asks once and remembers, and an AI agent's request asks first. A model over 100 MB is refused before anything is sent, and a failed upload shows as an error with the reason. The chat's privacy note said SolidCraft sends nothing anywhere, which isn't true any more, so it now says that about the chat. New strings are in every catalog. * Place Send to Slicer on the Insert Files panel |
||
|
|
30f951ecf9 |
Navigation bridge: a device's own app moves the view (#6094)
A homemade 3D input device's driver app (MAKMOUSE) listens on a loopback port; with Preferences > Navigation > "Navigate with a device app on this computer" on (off by default), the desktop app connects, says hello and reads JSON lines: axis motion (orbit, pan or zoom by mode), zoom steps and standard views. The motion goes through navigation::device like a 3D mouse's, only in the focused window; a HID device the app names is left to it. Documented in docs/3d-mouse.md. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
66b872a749 |
Web app: open a design from a same-origin path (#6670)
?open= now also accepts an absolute path on the page's own origin, fetched with the page's scheme, so a self-hosted setup that serves SolidCraft and other tools under one origin can link a design on a LAN without a public HTTPS address. Absolute URLs keep the HTTPS-only rule (a URL on the page's own origin is also accepted), redirects must end on HTTPS or the same origin, and protocol-relative, dot-segment, backslash and control-character values are refused. |
||
|
|
d895fef354 |
Control channel: a pushed design.changed event for clients that subscribe (#6985) (#6986)
* claim: #6646 * Control channel: document.revision, a design revision and a document id (#6646) A program that follows the open design asks document.revision and exports only when the answer moves: document_id names the open design (kept through edits, saves and renames, another for each design started or opened, never shared between two tabs or two runs) and revision moves whenever the design, what is hidden or an uncaptured component position changes. It stays put on reads, selections and view changes, unlike the session's view revision. The revision is read lazily: the session remembers the design it last reported (held weakly, so Arc::make_mut can never change it in place and its address can't be reused) and takes a new number from one counter for the whole run when the design is another one. The control method answers at once, also while a slow command computes; engine.execute reaches the same command for scripts and MCP. * document.revision: hash the run's id prefix with the process's random keys A clock set before 1970 gave every run the prefix 0, so a restarted app could answer a new design with the id and revision of the old one. The docs no longer promise an answer at once: a screenshot waiting for an opened design to be meshed still holds the requests behind it. * document.revision: its label in the complete catalogs (ja, zh) * claim: #6985 * Control channel: control.subscribe and design.changed events (#6985) A connection that sends control.subscribe gets the active design's id and revision in the reply (as document.revision answers), then a {"event":"design.changed","params":{document_id, revision}} line whenever either changes, so a live link need not poll. The window publishes the design at the end of each frame's logic and drawing while a connection is subscribed (one atomic load otherwise). Each subscribed connection waits on that from a thread of its own and writes the latest after a 100 ms gap, so a burst of changes is one line. Replies and events share one lock per connection, each written whole (#6390). * control.subscribe: the reply before any event, a second one refused The events thread started before the subscribe reply was written, so a change right after the window's answer could reach the client before the reply that named the older revision. The subscription now starts holding the connection's line lock and writes the reply under it. A second control.subscribe on a subscribed connection is refused on the connection itself: its answer could arrive behind a newer event. When no thread or socket is left for the events, the reply says it did not subscribe instead of ok. * control.subscribe: a second one does not lose a finished late reply The already-subscribed answer came before the busy check, so a request that timed out and had finished since lost its reply: control.pending then said the app ended it without one. It now comes after the busy check and drops a finished late reply as every other request does. * control_server: declare the subscribe guard at the top of the loop Clear of the lines #6643 changes (its token check moves to where the declaration was). * control_server: the subscription's items beside serve Away from the lines next to send, where #6643 adds token_of. * control test: New on a blank design reuses its tab, so redo the box first --------- Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> |
||
|
|
03bf992bc1 |
Keep linked components valid through Undo after Save As (#6860) (#6877)
* claim: #6860 * fix: preserve linked component targets across Save As history (#6860) |
||
|
|
c264c7a42a | Startup falls back from a Direct3D 12 that can't build the UI pipeline (#6648) (#6682) |