mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 03:51:08 +00:00
65b02ff7ab28eb60055ecd5e501ced94de8b1765
89 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
335278e3c7 |
Wave 266/509: Profiler: commands, feature evaluation, io; diagnostics.profile; CLI --profile (and 3 more)
4 commits, squashed; the originals stay reachable from tag archive/merge-main-develop. - |
||
|
|
5a4e482e9c |
Wave 260/509: Formatting: rustfmt over the rebased branch (and 3 more)
4 commits, squashed; the originals stay reachable from tag archive/merge-main-develop. - |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
c264c7a42a | Startup falls back from a Direct3D 12 that can't build the UI pipeline (#6648) (#6682) | ||
|
|
0c4eb458df |
macOS 3D mouse: unregister from the driver when the app ends, so the next launch gets the mouse (#6114) (#6115)
* claim: #6114 * macOS 3D mouse: unregister from the driver when the app ends, so the next launch gets the mouse (#6114) |
||
|
|
36a60a70d9 | Graphics: 1x anti-aliasing where the multisampled view draws nothing (#5819, #5870) (#5832) | ||
|
|
0344a41dc4 | Open and insert a DXF outside a sketch (#2052) (#4635) | ||
|
|
492bba9242 |
Control channel: write each reply in one piece (#6389) (#6390)
* claim: #6389 * perf: write each control reply to the socket in one piece The control server wrote each reply with writeln!(out, "{reply}") on the unbuffered TcpStream. Formatting a serde_json::Value hands the writer every string, number and punctuation mark separately, so each became its own write system call. A reply of a few megabytes (component.list on a design with thousands of components) took about half a second to send. Every reply is now serialized to one buffer and written at once. Writing one large Value to a loopback TcpStream: 1.79 MB took 1,052 ms with writeln! and 4.9 ms with one write_all. The test counts the write calls for a 2000-component reply (one) and reads it back. |
||
|
|
b1da434458 | Graphics: choose the graphics card, honour WGPU_ADAPTER_NAME (#5986) (#6010) | ||
|
|
093571cfe4 |
Connect AI apps at start-up, with a preference to turn it off (#6388)
At launch the app adds the solidcraft entry to each installed AI client off the window thread, names newly connected clients once, and refreshes a stale path. Settings > Assistant has "Make SolidCraft available to AI apps on this computer" (on by default); off removes the entries it added. Entries the user changed are left alone; automation runs never touch the files. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
16a5254e97 |
Fix macOS fullscreen application-bar interaction (#3643) (#3690)
* claim: #3643 * fix: keep macOS fullscreen application bar clickable (#3643) * test: load bundled fonts for fullscreen regression |
||
|
|
cc1e654690 |
Warn when a design is open elsewhere or changed on disk (#5467) (#5468)
* claim: #5467 * Warn when a design is open elsewhere or changed on disk (#5467) The desktop app keeps a lock file next to each open design (.~<file>.lock: computer, user, version, opened and refreshed times), refreshes it every minute and removes it on close; a lock not refreshed for three minutes is stale and ignored. Opening a design that another SolidCraft has open asks: Open Read-Only, Open Anyway or Cancel. A read-only design saves only to another file. A design remembers its file's size and modification time when it is opened or saved. Save over a file another program or computer changed since asks: Save a Copy (the default), Overwrite or Cancel. Programmatic calls never ask: doc.open reports open_elsewhere and takes read_only, and file.save over a changed file returns an error until it is given overwrite: true. ui.documents answers both questions. |
||
|
|
138bb0261c |
Windows OpenGL: use the system title bar so the picture lines up with clicks (#2063) (#4403)
* claim: #2063 * Windows OpenGL: use the system title bar so the picture lines up with clicks (#2063) On Windows with only OpenGL to start, the window opens with the system title bar instead of using the application bar as its title bar. The window without OS decorations keeps its caption style while having no frame, and some OpenGL drivers (older Intel HD/UHD) show the picture a caption's height lower than the window, so clicks land above what they point at. Direct3D 12 and Vulkan keep the custom title bar. The choice is made before the window opens, from the backends wgpu starts, so the window never changes its frame while OpenGL draws into it. |
||
|
|
a150822d9b |
Put solidcraft-cli on PATH from the AppImage and the macOS app (#6347)
The AppImage writes a ~/.local/bin/solidcraft-cli launcher from its desktop-integration step (same opt-outs, never over a file it did not write). The macOS app links the CLI into /opt/homebrew/bin or /usr/local/bin at launch when one is writable, and Help > Install solidcraft-cli command does it with an administrator prompt otherwise. Snap and Flatpak are documented. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
765c5fd143 |
Pictures drawn off the window thread finish before the process exits (#6062) (#6063)
A Home picture still being drawn by the detached saved-thumbnail thread could be on the GPU as the process exited, and the process died with SIGSEGV inside the graphics driver. capture::close stops new pictures and waits for those in flight (the app calls it in on_exit), and dropping SolveApp waits for the Home picture threads it started. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
05d43d3542 |
Windows: the taskbar button shows the mark when the first frames are slow (#6391)
The taskbar asks a window for its icon once, when the window appears. eframe sets the icon while the window is still hidden and shows it after the first frame; when that frame is slow (safe mode's software renderer) or startup kept the UI thread busy long enough for Windows to mark the window as not responding (building the viewport pipelines), the request isn't answered in time, the taskbar falls back to the window class's icon, and winit's class has none: the button showed Windows' generic icon for as long as the window was open. Give the class the exe's embedded icon (resource 1) before the window is shown. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2ff17c0fcb |
3D mouse: follow the device that moves, not the first one found (#3988) (#4339)
* claim: #3988 (3D mouse: follow the device that moves, not the first one found) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: follow the device that moves, not the first one found (#3988) On Windows and macOS the reader opened the first 3D-mouse interface it could and read it until it failed. A 3Dconnexion Universal Receiver with nothing paired opens fine, has a valid layout and never fails, so it kept the reader for good. A SpaceMouse on a cable next to it was never read ("Not calibrated (hid 256f:c652)", every axis at zero). The reader now opens every candidate and follows the one in use. The first is followed from the start, as before. Another takes over once it shows use (an axis off zero or a button down) while the followed one has shown none for STALE_AFTER, so two devices moved together don't trade the reading. A candidate that fails is dropped; if it was the followed one the reading resets and the next is read. With one device the reader behaves as before, down to the 100 ms read timeout. Several take 10 ms turns, and reports queue in the system meanwhile. The selection is `follow()`, written against a small `ReportSource` trait, so it is tested with simulated devices on every platform: an idle receiver next to a SpaceMouse, two devices in use at once, and devices failing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: a press that makes a device the one in use counts as a press Since #3825 a HID device's first button report after it connects is its baseline: a button down in it is held, not pressed. A device taking over from another one was connected at that moment, so the press that took over was swallowed. With an idle Universal Receiver found first, that is the first button press on the SpaceMouse. follow() now keeps the buttons each candidate last reported while another was read, and the reader hands them to the reading as the baseline when that candidate takes over. A device that reported no buttons before it took over keeps the held-not-pressed rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4cd1a51f48 |
Assistant: a request timeout per endpoint for slow models (#5009) (#5010)
* claim: #5009 * Assistant: a request timeout per endpoint for slow models (#5009) An OpenAI-compatible endpoint now stores how long one model request may take (request_timeout_s, 300 s by default, clamped to 30 s ..= 1 h). It is set in the Add agent dialog and in the endpoint's entry under Agents (a running conversation uses a change from its next request), through ui.agent_chat add_agent, and reported in the chat's state. A request that runs out of time says the limit and where to raise it instead of "timeout: global". The desktop HTTP cap goes from 900 s to an hour. The tool-call timeout stays 330 s: it covers SolidCraft's own tool work and the user's Allow, never the model. * Assistant settings: line up an endpoint's Request timeout with Permissions The label column of an agent's entry is wide enough for "Request timeout", so the label isn't cut and both rows share one column. |
||
|
|
b702ce8c7d |
fix: start the window on Intel Macs without wgpu's indirect-call validation (#3024) (#4808)
Co-authored-by: Nathalie Schönwetter <nathalie@cfw-ag.ch> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
0520910bfd |
Control: name the token file and spot a stale one when a call is unauthorized (#4664) (#4670)
* claim: #4664 * Control: name the token file and spot a stale one when a call is unauthorized (#4664) When the app refuses a token that the MCP client read from a token file, the error now names that file, when it was written and how long ago, without printing the token. The app also writes `<port>.pid` beside the token file (its process id and, on Linux, its start time) and removes both at exit; when that process has exited, the error says the token file is stale. The process check runs only after a refusal, and the token file itself stays one line, so existing clients keep working. Document force-quit leftovers and the MSIX AppData redirection. * Bench assistant: read the token out of the new ClientToken Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> --------- Co-authored-by: bherbruck <brennen.herbruck@gmail.com> Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
4f4b1fc2d2 | Sketch: select edge midpoints for horizontal and vertical constraints (#5159) | ||
|
|
9e6e0b8df1 |
Render workspace: complete experimental ribbon, resources and effects integration (#5493) (#5494)
* wip(render): reconcile completed material and image pipeline source with 0.20 (#5493) Source-only integration reference for staged review, not a merge-ready aggregate. Donor: 0xlillith/solidcraft@aea20b0115. Upstream: |
||
|
|
a633a67e63 |
Preferences: choose the window system on Linux (Wayland or X11) (#3083) (#3084)
* claim: #3083 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * WIP: choose the window system on Linux in Preferences (#3083) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Preferences: a window system set over the control channel is saved for the next start again The merge kept main's check in ui.prefs, which only looked at the anti-aliasing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Preferences: the window system section drops the restart badge its note already states Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
1f6a2d4daf |
3D mouse: driver choice, guided calibration, button mapping and a picture of the device (#1463) (#3825)
* Add a small test boat to steer around with the 3D mouse A little hull in its own camera, drawn by the UI itself and not the CAD viewport, so a 3D mouse can be tried out without opening a design. It brings the model (generated by assets/models/make_test_boat.py) and the tests for how the camera moves it. Nothing uses it yet; Preferences picks it up with the rest of the 3D mouse work. * UI kit: keep a choice row from flipping with the scrollbar A choice row picks segments or a dropdown by whether its labels fit. A page scrollbar that comes and goes narrows the row by its own width, so the row could flip back and forth between the two on a page that sits right at the edge. It is now judged on the whole row and through a small helper, fits_steadily, that only changes its answer once the miss is wider than the scrollbar. Also tests for that, for typed numbers in a slider's value box, and a test hook to put up the issue-report input barrier by hand. * 3D mouse: device model, guided calibration, buttons and a picture The 3D mouse becomes a device we know something about instead of six raw numbers. The reader picks between spacenavd and the direct hidraw driver, falls back from one to the other, and says so when it can't open the device. Guided calibration asks whether the device is a spring cap or a trackball, adds a touch step, and uses a settle filter so a ball let go of stops reading as motion. Buttons get jobs (keys, held modifiers, commands), saved per device, with defaults for the SpaceMouse Pro. Preferences gets a picture of the device that moves as it does, next to the test boat, and the same picture can sit in the 3D view at Left Viewport, Right Viewport or Left Window, above the floating History card like the legend and readouts. These ride together because the crate's calibration types, the Preferences page, the Linux reader and the button hook all use each other. * 3D mouse: offer to calibrate an uncalibrated device When a 3D mouse shows up that has not been calibrated, a card in the 3D view offers to calibrate it, to swap drivers when the other one would work better, to close, or to never ask again. It is made once per device, goes away after 30 seconds in view, and gives way to a status or computing toast instead of stacking on it. The card is the shared toast, which now takes buttons: they group by purpose, wrap inside the card at narrow widths, and do nothing while an issue report holds CAD input. The picture of the device in the 3D view keeps clear of the card. * 3D mouse: translations and attribution The 3D mouse strings (ui.3d_mouse_*, the calibration steps and the offer card) in every shipped language, the Japanese catalog note, and the credit for the test boat model in ATTRIBUTION.md. This is last because the code before it only looks keys up: it builds and runs without these, falling back to English until they land. * 3D mouse: draw its preferences with the settings kit like the rest of Navigation The 3D mouse section was a folded header with its own layout. It now uses the same kit as the sections above it: a settings_section card, settings_form rows with the shared label column, dividers between the device, axes, picture and buttons groups, Text::Hint for the helper lines, and settings_control_width for the dropdowns. The section is open like every other one (the header still folds it), so the device badge on the folded header is gone; the calibration row already names the device. Every setting, string and key is unchanged. Tests that unfolded the section no longer click it, and the hint lines are looked up as hints instead of notes. * 3D mouse: show each axis's live tilt in Preferences, like main's axis rows Each view axis row is main's axis row again: a meter that follows the tilt of the motion driving it, with the dead zone marked, next to Invert, Speed and the "driven by" menu. The meter reads the calibrated travel when the device has a calibration and the raw reading over full scale when it does not, and asks for a frame whenever the reading changes. The helpers are back as they are on main, and the calibration state is a badge by the enable toggle. * 3D mouse: line its preferences up with the rest of Navigation - The two checkboxes sit side by side in the same two-column grid as Wheel and trackpad. - Row labels stop repeating "3D mouse" and use sentence case: Calibration, Driver, Restore defaults, Reset view, Left viewport, Right viewport, Left window. - Hints line up under the control column instead of the left edge. - The six view axes are named Pan X, Pan Y, Zoom, Orbit X, Orbit Y and Roll so they fit the name column. - Buttons are a divider and rows in a form like the other groups, without the bold sub-heading. - The driver, dead zone, axes, picture and buttons sit behind a collapsed Advanced section, since most people never touch them. * 3D mouse: tidy the Advanced test for clippy |
||
|
|
6c6ffe4802 |
MCP: wait out long requests, honest batch outcomes, batch tool aliases (#2901)
Rebased onto main as one commit (was |
||
|
|
98c4f335e8 |
Control channel and MCP on by default, with a setting to turn it off (#5561)
The app opens its localhost control channel on port 27878 (or the next free one) unless Preferences > Assistant turns it off; --control and SOLIDCRAFT_CONTROL_PORT still win. A current.port discovery file lets solidcraft-cli mcp find the running app with no arguments. Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
945e6261cc |
3D mouse test: clear the frame's texture changes, so it passes in debug builds (#4382)
each_frame_hands_the_view_its_motion_through_the_drivers_mapping dropped
the output of a frame it ran (`let _ = ctx.run_ui(...)`). epaint panics
in debug builds when a TexturesDelta is dropped with deltas left ("Dropped
TexturesDelta with 1 unapplied deltas"), so the test failed under a plain
`cargo test` on every platform while the ci profile passed. It now clears
them, as the other UI tests do since #60.
Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
|
||
|
|
fdde3f9da9 |
Add macOS native menu bar (#2361) (#4531)
* claim: #2361 Co-Authored-By: GitHub Copilot <noreply@github.com> * feat: add native macOS menu bar (#2361) Reuse document actions and unsaved quit handling in native application, File, Edit, Window and Help menus. Replace duplicate header controls only when native installation succeeds, preserving other platforms and the fallback UI. Co-Authored-By: GitHub Copilot <noreply@github.com> --------- Co-authored-by: GitHub Copilot <noreply@github.com> |
||
|
|
b31fddb98e |
OpenGL: the viewport scene renders 1x again, like the window (#5605)
#5187 gave the scene its own offscreen target with the app's sample count,
which skipped the OpenGL clamp to 1x from
|
||
|
|
5962f56f87 |
Viewport: reuse still pictures and draw scenes into a 1x window pass (#4820) (#5187)
* Viewport: show the picture it drew last when nothing changed (#4820) A frame redrew the whole scene whenever egui asked for a frame, even when the camera, scene, highlight and overlays were identical: pointer moves over empty space and UI animations over a still view paid for the full scene. The renderer now hashes everything the picture depends on (scene keys, camera, size, section, images, analysis, accessibility map, occlusion) and blits the last offscreen picture when the hash matches. The first still frame filters a supersampled picture down once and later frames copy it as it is. A frame with the same content costs a blit instead of a scene draw. Co-Authored-By: Claude Code <noreply@anthropic.com> * Viewport: draw the scene offscreen, into a 1x window pass (#4820) The scene keeps the app's depth, stencil and MSAA, but egui's window pass now has one sample and no depth. Whenever the two targets differ, the scene (product previews too) is drawn offscreen and blitted, so the UI pass no longer clears and resolves a multisampled window. Over 8 s of pointer moves on the 450E rotor head, the app's GPU intervals fell from 1325 ms to 1006 ms and egui_render from 892 ms to 509 ms; idle stays at 0%. A GPU test checks the split path's picture matches the direct one. Co-Authored-By: Claude Code <noreply@anthropic.com> * 3D mouse: a repeated report at rest asks for no frame (#4820) A device may repeat its latest report while the cap rests. Publishing it again asked egui for a frame although nothing changed. Only a report that changes the reading now asks for one; while the cap moves, the view asks for the next frame itself. Co-Authored-By: Claude Code <noreply@anthropic.com> --------- Co-authored-by: Claude Code <noreply@anthropic.com> |
||
|
|
17f0d6bc68 |
Format registry: one table of formats, extensions and options (#5215) (#5217)
* Add the format registry: one table of formats, extensions and options in crates/io. The hosts' Open-dialog filters and the web accept list derive from it, and registry tests pin it to the path predicates and the command docs. * Fix format-registry tests that compared FormatSpec without Eq. assert_eq on Option<&FormatSpec> does not compile; the none cases only need is_none, matching the other rows that compare ids. |
||
|
|
92d3a2fde5 |
GPU: a lost device stops drawing instead of crashing (#4172) (#4229)
* claim: #4172 * GPU: a lost device stops drawing instead of crashing (#4172) After the driver resets the GPU (Windows TDR), the first queue write to meet the lost device panicked inside wgpu-core (PendingWrites::activate unwrapping DeviceError::Lost). Nothing watched the device for its loss. - egui-wgpu: RenderState::device_lost (DeviceLost) is set by the device's lost callback. The painter draws nothing on a lost device, and before a frame's uploads it submits nothing (DeviceLost::probe), so a loss the driver reports is found by that submission instead of the first write. - Pictures (thumbnails of saves, ui.render) watch the same flag: on a lost device the capturer refuses with GpuError::DeviceLost without touching it, and the picture comes from the CPU rasterizer. Headless capturers watch their own devices. - The app, seeing the loss, closes keeping unsaved changes for recovery (as a forced quit does) and starts again; the new start says what happened. A second loss within ten minutes starts on the software renderer. The loss is noted in the crash journal. |
||
|
|
6f5d0038ab |
3D mouse: find devices on Windows without asking every HID device for its strings (#2614) (#3653)
* claim: #2614 (3D mouse: find devices on Windows without asking every HID device for its strings) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: find devices on Windows without asking every HID device for its strings (#2614) On Windows the 3D mouse reader rescanned every two seconds while no 3D mouse was plugged in, with hidapi's refresh_devices(). hidapi's Windows enumeration opens every HID interface and reads its serial number, manufacturer and product strings: on USB, three control requests to every keyboard, mouse and touchscreen, forever, on a machine without a 3D mouse. That fits the report of an idle machine whose USB keyboard drops off and returns and whose cursor jumps. Windows now asks Plug and Play which present HID interfaces are multi-axis controllers (hardware ID HID_DEVICE_UP:0001_U:0008), which sends nothing to any device, and opens only those with open_path(). hidapi's own discovery is turned off before the reader makes its one context, so making it touches no device either. macOS keeps hidapi's enumeration (IOKit's registry); Linux is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * AGENTS.md: list the Plug and Play scan among the files allowed unsafe code (#2614) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com> |
||
|
|
f22a026c27 |
3D mouse: choose which motion drives each axis (#2782)
* 3D mouse calibration: find each axis from a guided recording (#1463) A recording of the cap at rest and moved along each axis both ways gives which raw axis is which, which way it runs, where it rests, how much it wanders at rest and how far it goes each way. Hands aren't exact: on our SpaceMouse Pro, sliding the cap right moved Ry further than X. So each raw axis is picked by the one-to-one assignment that best explains all six motions together, each raw axis scored against its own range so a wider one doesn't win through cross-talk. Each direction is scaled on its own, so a device that goes further one way still reaches full scale both ways; a direction under a twelfth of the furthest axis counts as not having responded. A small state machine walks the user through it and moves on by itself once the cap has been held out for 0.4 s in one go and let go. "Held out" is the least the solver accepts, twice an axis's own rest wander, so whatever the solver would accept can be recorded and a noisy axis doesn't hold up a quiet one. A calibration also carries a dead zone the user sets, a share of full deflection taken off the bottom of the travel, with full deflection still reading full scale. Serde support is behind an optional feature, so the crate still needs nothing by default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse calibration in Preferences, saved per device (#1463) Preferences > Navigation gets a 3D mouse calibration row: whether the device being read is calibrated, Calibrate... and Forget calibration. The window asks for each motion in turn and saves what it found under the device (hid, web, spacenavd or 3dxware, with vendor and product ids), so switching devices or turning spacenavd on doesn't apply the wrong mapping. A calibration is saved for the device it started with, and ends if another one is read instead. navigate_frame puts the reading through the device's calibration before the driver mapping, so the per-axis Invert and speed and everything after them are unchanged. While the window is open the reading goes to it and the view stays put; it closes with Preferences. Restore Defaults on the Navigation page keeps calibrations, since they describe the device. The web app hands over the gamepad's readings as they are, so calibration sees where the cap rests; navigate_frame now leaves a reading inside the dead zone alone after calibration, so a cap resting just off zero still doesn't keep redrawing. Whether the cap is moving is asked of the calibrated reading there, in the web page's idle waker and in both wheel filters. Those run before the frame, so they keep the last frame's calibration with its device and use it only for that device. On the desktop, an axis with nothing fresh (timed out, or not reported since the device connected) reads as the device's calibrated rest, so a device resting off zero doesn't drift. A saved calibration that can't be read or used (a hand edit) is dropped on its own when the preferences load, with a log line. The window shows the device's raw readings live and has a dead zone slider, a share of full travel on top of the measured noise; it's saved with the calibration and adjustable in Preferences afterwards. New strings are in all 14 catalogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: choose which motion drives each axis (#1463) After calibrating, which motion of the cap should drive which view axis is partly a matter of taste: rolling and twisting swapped, say, or Y and Z. Each axis row in Preferences > Navigation now has a "driven by" choice next to Invert and speed. Picking a motion another axis uses swaps the two, so each motion always drives exactly one axis, and Reset axes puts them back. The choice applies after the device's calibration and before the driver mapping, so Invert and speed stay with the view axis rather than moving with the motion. It's a preference, not part of a device's calibration, and stays in force when recalibrating; the calibration window says so while one is set. A saved assignment that doesn't drive each axis once loads as the default. New strings are in all 14 catalogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: axis choice review follow-ups (#1463) - An axis's speed, like its Invert, stays with the view axis when a motion is reassigned; tested. - Calibrating records the device's own reading, before the axis choice, so a saved swap isn't baked into a new calibration and then applied twice; tested through a whole calibration. - A settings file without the axis choice, or with one of the wrong length, loads with the default and keeps every other setting; tested. - Each axis row says "driven by" in sight before its choice, which otherwise only explained itself on hover (no help on a touchscreen or the web). - "Reset axes" is now "Reset axis assignment", so it doesn't read as forgetting the calibration just below it; the "driven by" tip reads more clearly in English. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: name axis rows by what they do, and motions without a device in mind (#1463) The axis rows and the "driven by" choices showed the same names, "Push left / right" and so on, which read as one thing mapped to itself, and they described a cap that springs back to centre, which a trackball or a knob that stays put isn't. Each row is now named for what it does to the view: Pan left / right, Zoom, Pan up / down, Orbit up / down, Orbit left / right or Roll, or Off. The name comes from the driver mapping navigation used last frame, so it stays right where the mapping swaps axes (object mode swaps the last two). Before navigation has run there's no mapping to read, and a row is named for its own motion instead of a guessed effect. The choices are the device's calibrated motions: Translation X / Y / Z and Rotation X / Y / Z, under a header row, and the calibration window names a motion that barely moved the same way. This renames #1814's existing rows, which had no translations; every new name is in all 14 catalogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: rows never fall back to a motion name (#1463) Without a driver mapping, a row was named for its own calibrated motion, which is also what its dropdown lists. It now takes the default mapping's view axis, so the two sides never share a name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: grey out Invert and speed on rows the driver turns off (#1463) A row the driver profile turns off says so on hover, and its Invert and speed are greyed out, since they do nothing there. Its motion choice stays live so a motion can still be swapped out. The column header is shorter. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse: unmapped rows say Axis 1-6, and the off-row tip explains the swap (#1463) Without a driver mapping, a row is now just "Axis n" rather than the default mapping's view axis, which would be a guess there. The tip on a row the driver turns off says that a motion swapped into it stops moving the view, and a test swaps one in and back out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * 3D mouse calibration: a device that stops short of rest after a release (#2778) Through spacenavd, a relative-axis device like the Space Navigator never reports its return to zero, so the reading stays at its last small report after every release. The wizard never saw the cap let go, and once calibrated, the stuck value crept the view. A reading that stays exactly the same, close to rest, now counts as let go, and where the cap settled after each step goes into the calibration: such a device rests at the middle of where it settled, with a dead zone out to the furthest of them. A device that comes back to rest calibrates as before. * 3D mouse calibration: a steady hold through spacenavd stays a hold (#2778) * 3D mouse calibration: stopping short of rest only counts from spacenavd and 3DxWare (#2778) Recentring on where the cap settled kept the rest stage's wander radius but not its offset, so a rest extreme could fall outside the dead zone; the radius now grows by how far the centre moved. A reading that stops changing close to rest only ends a step from a source that sends nothing at rest, so HID and web devices calibrate exactly as before. * 3D mouse calibration: settling off-centre counts on any device (#2778) A drifting stick settles off its rest after a release just as spacenavd's stuck reading does, but it keeps reporting, noisily. A step now ends once the reading stays steady, each raw axis within twice its rest wander (so exactly still from a silent source), close to rest, on any device. The dead zone takes that wander on top of the furthest settle point. * 3D mouse calibration: steadiness by the run's spread, settled on the step's own axis (#2778) An axis that read flat at rest may dither once off-centre, so steady readings may spread at least 2 units and a 50th of the furthest any step went. Steadiness is the spread of the whole run, not distance from its first reading. Close to rest is judged on the axis that set the step's peak, since a device's axes may have different ranges, and the settle point is the middle of the steady run. * 3D mouse calibration: readings close to rest never count as held (#2778) * 3D mouse calibration: the dead zone reaches every reading of each release (#2778) Saving one reading of each release and padding it by the rest wander could miss a reading seen in the same steady run. Each release now keeps the whole run, and the dead zone is centred on and reaches everything read at rest and after the releases. * 3D mouse calibration: settle for half a second before a step counts as let go (#2778) A hand held still close to rest is less likely to end a step early; a device that comes back to rest never waits for it. * 3D mouse calibration: show the dead zones as a game controller tester would (#2778) Once calibrated, the window, and Preferences under the Dead zone slider, show three pads: slide, press and twist, and tilt. Each has a circle for the full travel, the dead zone shaded inside it with each direction its own size and the user's dead zone on top, and a dot where the cap is now, lit once the view moves. * 3D mouse calibration: settle through cross-talk, per-axis steadiness floor (#2778) The axis that went furthest in a step needn't be the one asked for: cross-talk can move another raw axis further. A step now counts as let go once the furthest axis is at least halfway back and some axis that moved about as far is within a quarter of its own peak; a hand still holding the push keeps the furthest axis out. The steadiness floor follows each axis's own reach, and each stage's steadiness starts with its own readings. * 3D mouse calibration: steady readings may dither ±2 on any axis (#2778) * space-mouse: make the settle check's near band match the steadiness spread, test the 4-unit floor near() measured distance from the settled run's middle against the whole tolerance, so it accepted twice the band the steadiness check does. Use half. Add a short-range test where only the .max(4.0) floor lets a +-2 dither settle, and one where dither the spread check rejects is also not skipped as untouched. * space-mouse: settle on the middle of the steady run so a lopsided dither isnt movement near() allows half the steadiness spread around the settled point, but the point was the run mean. A dither that is mostly one value with an occasional far one (10, 10, 10, 14) is steady, yet the far reading sat 3 from the mean and started a hold in the next step. Use the middle of the run range instead. Adds a lopsided-dither test and a test pinning the documented cross-talk limit ([100,300] then [5,90] stays open). * Judge an untouched cap by the settled run's spread, not a rounded midpoint Rounding the midpoint to i16 could leave one end of an odd-width run more than half the tolerance away, so the cap read as touched while sitting at the end of the run it settled on. Keep the run itself and ask whether one more reading keeps its spread within tolerance. * space-mouse: pin the wider skip band of a narrow settled run and a single-reading settle * space-mouse: keep a step held while a lesser axis stays out as the larger one relaxes Settling needed one axis furthest and returned, and one axis about as far and settled, which a single relaxed cross-talk axis satisfied alone. Every axis that moved at least half as far as the furthest now has to be back by RETURNED_SHARE of its own peak too. * 3D mouse: the calibration tests run in a focused window, as the focus check now requires * 3D mouse: the axis tests run in a focused window, as the focus check now requires * 3D mouse: the calibrated-rest test runs in a focused window, as the focus check now requires * Settle a spacenavd reading that is nearly at rest (#3523) spacenavd sends a record only when the values change and nothing when the cap springs back, so the last small leftover of a release was held forever and the view kept drifting. A held reading now reads as rest once every axis is within 150 raw units of the device rest and no record has arrived for 0.5 s. A firmer push stays as long as it lasts, and so does a small value next to a large one. 150 is the smallest round number above the worst leftover in the issue (129 on one axis). * Choose which 3D mouse driver to read on Linux When both spacenavd and the device itself (hidraw) are there, Preferences now has a "3D mouse driver" choice: Automatic, spacenavd or Direct. Automatic is what it always was, spacenavd when it runs and the device otherwise, so nobody's setup changes. If the chosen one isn't available the other is read and the log says so once. Each driver keeps its own calibration, as before. To switch without a restart both reads now wake up when idle and look at the choice: spacenavd with a 250 ms read timeout, hidraw opened non-blocking and polled every 5 ms when it has nothing. A record that arrives is read as before, and one split across a timeout is kept. The reader also publishes which of the two exist, which is what shows the combo box; with only one it says which one is in use. * Build the 3D mouse row header from existing labels --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: bherbruck <brennen.herbruck@gmail.com> |
||
|
|
e570c0b4eb |
Windows: a restored window that is out of reach is fitted to the monitor's work area (#4299)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
463cea0615 |
Windows: ask for the foreground once, when the first frame is on screen (#4284)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
0225e4d2a5 |
Startup: the first frame no longer waits for the system CJK/Hangul/Thai font search; SOLIDCRAFT_PROFILE_STARTUP prints startup steps (#4283)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
|
|
fdec948abc |
Control channel: control.running lists what the app is running, across connections (#3654) (#3689)
* claim: #3654 * Control channel: control.running lists what the app is running, across connections (#3654) control.pending answers only for the connection that asks, so a second client, the MCP bridge after a reconnect or a script saw {"pending": null} while the app was busy. control.running answers for the whole app, the same on every connection and at once (the connection's own thread answers it, also while it is busy with a timed-out request): every request the app has been sent and not answered yet, oldest first, with its connection, id, method, command id (or a batch's step count), seconds since it arrived and whether it still waits its turn. A timed-out request stays listed until the app answers it, also after its connection closed. Parameters are never shown. The window marks a request started when it takes it (ControlRequest.started), which tells queued from running. The MCP server gets a read-only `running` tool. --------- Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> |
||
|
|
6e22e3709d | Web: reopen designs saved on the computer from the recent list after a reload; forget stale remembered files (#104) (#3834) | ||
|
|
b2168435ba |
Web: save to and open from files on your computer (#104) (#3681)
Save As and Open use the browser's file dialogs where the File System Access API exists; Save writes back to the same file, its handle kept in IndexedDB across reloads. File > Download design added. Other browsers keep storing designs in OPFS. |
||
|
|
9e7a7b260b |
perf: Save and Save As write on a worker thread (#3044) (#3146)
Save ran on the window's thread: about 130 ms on the Prusa Core One design, and more than a second once saves keep the evaluated model. The toolbar's Save and Save As now run the command on a scratch copy of the session on a worker; the design waits for it as it waits for a file picker (commands are refused meanwhile), and the outcome is taken up as running the command here would have left it: the name and links the save gave the design, its file, its saved state (`Session::adopt_save`), the echo, the error status, the recent files and the home thumbnail. A tab closing or the app quitting after Save waits for the write, as does the app's exit. Scripts, the control channel and the browser build still save in place. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d3fa22e987 |
3D mouse calibration in Preferences (#2326)
Adds a 3D mouse calibration section to Preferences (#1463). |
||
|
|
4c30d944f5 |
3D mouse: only the window with focus moves (#2077) (#2212)
With two windows open, the 3D mouse now moves only the one with focus. |
||
|
|
1666ca7018 |
Fix the macOS launch hang with the 3Dconnexion driver installed (#2841)
Thanks @abteenz. |
||
|
|
dc27988bc2 |
Agent chat: PDFs as documents, any other file by reference (#2812) (#2858)
A PDF goes whole to agents that take embedded documents; to the others, or past 12 MB, the message names it with its type, size and place. Any file that is not a picture, text or CAD file is attached by reference instead of refused. Chips show the type's extension; the file dialog offers All files. |