Commit Graph

338 Commits

Author SHA1 Message Date
jyviko d1b64fb2ff Wave 273/509: Continuity in words: surface dialogs and Continuity Analysis show no G codes (and 3 more)
4 commits, squashed; the originals stay reachable from tag archive/merge-main-develop.

- 28e3b8a19 jyviko: Continuity in words: surface dialogs and Continuity Analysis show no G codes
- 81ecc872f jyviko: Guide: the Fillet dialog's Type and Corners in their new words
- d1a8f0417 jyviko: Thread: a boss thread keeps clear of the plate below
- a30a85698 jyviko: Thread: the chamfered tip reaches ISO 724's external minor diameter

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
2026-10-27 01:58:51 -04:00
jyviko 57b4ce6453 Wave 267/509: geom: fill_matched, a height field G1/G2 to the faces beside its loop (and 2 more)
3 commits, squashed; the originals stay reachable from tag archive/merge-main-develop.

- faffa6664 jyviko: Profiler: the diagnostics.profile test holds while other tests record
- 7fde0bdff jyviko: Profiler: docs/perf.md says how to switch it on and read it
- 29225d2c8 jyviko: geom: fill_matched, a height field G1/G2 to the faces beside its loop

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
2026-10-26 12:32:39 -04:00
jyviko 88736dc9e4 Wave 259/509: thread: main's screw direction (#4414) out; the thread's hand is the one direction (and 2 more)
3 commits, squashed; the originals stay reachable from tag archive/merge-main-develop.

- a57ce13e7 jyviko: thread: a shaft thicker than a modelled thread is refused, never turned down
- 6b9f5e0e4 jyviko: thread: main's screw direction (#4414) out; the thread's hand is the one direction
- 5fc160732 jyviko: Main's new callers of the placing calls that now return their error

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ
2026-10-26 19:21:58 -04:00
Victor Santos Oliveira 6e12f5954f Navigation: snap free orbit direction and roll with Shift (#6943) (#6949)
* claim: #6943 free orbit Shift snapping

* fix(ui): snap free orbit direction and roll with Shift

* test(ui): align free orbit coverage and complete snap help translations
2026-10-27 04:11:16 +00:00
Lars Jansen 2d629069c6 Dialogs: OK and Cancel at the top (#6708) (#6713)
* claim: #6708

* feat(dialogs): OK and Cancel at the top (#6708)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 02:42:23 +00:00
Lars Jansen f1cc49bcdb History: list a sketch under the feature built from it (#6489) (#6494)
* claim: #6489

* feat(history): list a sketch under the feature built from it (#6489)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:59:57 +00:00
The Nomad 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
2026-10-26 19:59:35 -04:00
bherbruck c391ef17cb Themes: Clay (light) and Clay Dark, warm paper and charcoal (#7342)
* Themes: Clay (light) and Clay Dark, warm paper and charcoal with a terracotta accent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Clay: on-accent text a touch lighter for 4.5:1 on the accent

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:31:40 +00:00
Jesper We 436ee95095 Preference: return to the previous view when a sketch ends (#7038) (#7043)
* claim: #7038

* Preference: where the view goes when a sketch ends (#7038)
2026-10-26 19:31:55 +00:00
amolinari-autotex 47285ccb88 Thread: right- and left-handed direction (#4414) (#4415)
* claim: #4414

* feat: persist right and left thread handedness (#4414)

* fix(thread): verify handedness with referenced holes and complete locales

* test: verify handedness with merged tapped thread catalogues

* test: retain tapped thread depth when changing handedness (#4414)

* test: restrict Unix symlink coverage to Unix targets

* test: inspect hole thread depth inside edit command parameters (#4414)

* test(doc): initialize cylinder radius in handedness fixture
2026-10-26 13:57:08 +00:00
froglemonade2 e063a7778e Axes and points from circular edges and round faces (#3128) (#4610)
* Axes and points from circular edges and round faces

In 3D, a round edge or face stands for its circle wherever a command
takes an axis or a point.

Revolve, Surface Revolve, both Circular Pattern dialogs, Plane at Angle
and Move -> Rotate take a circular edge (through its centre, along its
normal) or a cylinder, cone or torus face (its axis). The features keep
the pick as a model reference, so they follow the model when a parameter
moves it. Straight edges work as before; flat faces are refused.

Every 3D point input takes a circle's centre: construction points (which
follow the circle), To Object, patch points, hole placement, and Move's
pivot and points. Hovering shows the axis as a dashed construction line
or the centre as a star, and the dialog's chip names what the pick
stands for ("centre of edge 10", "axis of face 3").

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Format the round-pick hover check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Selection chips: name a round pick as a circle centre or axis

The chip has room for little more than the body and a word or two:
"Shaft · circle centre", "Shaft · circle axis", "Shaft · face 2 axis",
and "Shaft · point" for a point of the model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Move: a circle's centre in a placed component is where the component shows it

Move's pivot and points read a picked circle from the model in its
component's own frame, while the pick is in the world. They now read it
from the world state (and, editing a Move, from the state before it,
placed the same way), as the viewport and the other round picks do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Plane at Angle on a planar face about a model edge: test that it follows both

Upstream #1956 made a planar face a base for Plane at Angle; this branch makes a model edge an
axis. The rebase keeps both in one PlaneRef::AtAngle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:27:32 +00:00
ivisaf 3bcf77ee32 Expressions: a number and its unit are one value (10 mm / 2 mm is 5) (#6331)
* Expressions: a number and its unit are one value (10 mm / 2 mm is 5)

A unit written after a number was multiplied in at the precedence of `*` and
`/`, so `10 mm / 2 mm` was (10 mm / 2) mm = 5 mm² and `90 deg / 1 rad` a value
in angle², reported as a unit error. The number now takes the unit after it
before `*` and `/` apply; a fraction of numbers before a unit stays one value
(`1/2 in` is half an inch), and an exponent stays a plain number (`2^3 mm`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Expressions: after a division, a fraction before a unit only when closed up

`width / 10 / 2 mm` read the `10 / 2 mm` as the fraction 5 mm and gave
width / (5 mm) = 8 instead of (width / 10) / 2 mm = 2. After a `/` the
fraction rule now needs the fraction written without spaces (`1 in / 1/2 in`
is still 2); elsewhere `1/2 in` and `10 / 2 mm` are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 12:24:34 +00:00
Victor Santos Oliveira 91638cb1f3 Add practical command guides and a contribution template (#5237) (#6772)
* claim: #5237 command-level guide follow-up

* docs: add practical command guides and resource contributions (#5237)

* docs: index practical guides and reject incomplete command links
2026-10-26 12:24:23 +00:00
raykholo 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.
2026-10-26 12:23:52 +00:00
rlartot c7e53899af Updated AppImage takes the release file name (#3538) (#3658)
* claim: #3538

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Updated AppImage takes the release's file name (#3538)

A versioned AppImage name (solidcraft-<version>-linux-<arch>.AppImage) is
replaced by the new release's name, so zsync-based tools keep finding the
update data. Other names are kept.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 11:58:47 +00:00
Dahak Builds d3720041b6 CI tooling: opt-in bound for local test concurrency (#1188) (#6831)
* claim: #1188

* #1188: split per-binary test threads and matrix workers under SOLIDCRAFT_TEST_JOBS

---------

Co-authored-by: Dahak Builds <dahakbuilds@users.noreply.github.com>
2026-10-26 11:50:10 +00:00
Homero Thompson 72a76edcf7 Shortcuts: Ctrl+Shift+S opens Save As in the workspace (#6558) (#6567)
* claim: #6558

* Shortcuts: Ctrl+Shift+S opens Save As in the workspace (#6558)

The Home page already opened Save As with Ctrl+Shift+S, but the design workspace matched Ctrl+S, so the chord saved over the current file instead of offering another one. The workspace now routes the chord to Save As, and the File menu shows that shortcut next to Save As, taken from the command registry.
2026-10-26 11:14:07 +00:00
raykholo c264c7a42a Startup falls back from a Direct3D 12 that can't build the UI pipeline (#6648) (#6682) 2026-10-26 10:44:13 +00:00
Homero Thompson 2ec11cfc3a Docs: how to open the macOS build when the Gatekeeper blocks it (#5393) (#6566)
* claim: #5393

* docs: how to open the macOS build when the Gatekeeper blocks it (#5393)
2026-10-26 10:41:32 +00:00
Lars Jansen 4a0d519727 View: OK and Cancel in the corner of the view while a dialog or sketch is open (#6709) (#6714)
* claim: #6709

* feat(view): OK and Cancel in the corner of the view while a dialog or sketch is open (#6709)
2026-10-26 10:40:36 +00:00
Thosch 06af6e1107 Pattern dialogs: Quantity is the count in every language, the motion plot keeps its own entry (#6538) (#6733)
The catalogue entry Quantity served both the pattern dialogs' number of
instances and the motion study's plotted quantity. Nine locales had
translated the second meaning, so the rectangular pattern dialog showed
e.g. "Größe" above "Anzahl 2" in German.

The motion study's row now reads motion.plot_quantity (a pending file
that keeps every locale's previous word there). Quantity takes each
catalogue's own ui.quantity text, which the "Quantity 2" row already
shows; nothing is newly translated. The glossary lists Quantity under
words with more than one meaning.

Fixes #6538

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:40:26 +00:00
sascha2101 8108646e2c Docs: a sketch family's shortcut starts the variant used last (Refs #6630) (#6817)
Since #4308, L, R and C start their family in the variant used last, as
the toolbar button does, so L can start a Midpoint Line. The shortcuts
table still read "L | Line". Say so under the table and in the
sketching guide's Geometry section, with the preference that steps
through the variants.


Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq

Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:39:51 +00:00
Thosch 102314dbb2 Docs: blurry interface on Linux with display scaling, and how to fix it (#5979) (#6195)
SolidCraft runs through XWayland whenever an X display is available. A
compositor that doesn't let X11 apps scale themselves draws the window at
100 % and stretches it. docs/nix.md said so only for fractional scaling,
but GNOME 47 (RHEL and Rocky Linux 10) does it at any scale: the report
shows 1.05 physical pixels per point at 200 %.

The troubleshooting page gets a section with the cause, the Window system
preference, SOLIDCRAFT_WAYLAND=1 for 0.21.0 and earlier, and the file drop
trade-off; docs/nix.md links to it.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 10:24:30 +00:00
raykholo d29e70bad9 docs: Save As blocked by Windows Controlled folder access (#4490)
Saving into Documents fails with the Windows dialog's "File not found" when
Controlled folder access blocks the app, and the message gives no hint.
Refs #4479.
2026-10-26 06:49:53 +00:00
Logan Night 1d482aff74 Docs: AppImageLauncher 2.x can't start the AppImage; update to 3.0 (#4640) (#4674)
* claim: #4640

* Docs: AppImageLauncher 2.x can't start the AppImage; update to 3.0 (#4640)

AppImageLauncher 2.x intercepts every AppImage through binfmt_misc and
can't start static-runtime AppImages such as SolidCraft's: it fails with
"execv error: No such file or directory" after a FUSE message. 3.0 starts
it. One paragraph in the Linux AppImage section says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Docs: AppImageLauncher 3.0 is so far only published as betas (#4640)

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
2026-10-26 06:49:52 +00:00
raykholo 2a01cca00e User guide: local models with the Assistant (#4851) (#4964)
* claim: #4851

* User guide: local models with the Assistant (#4851)

A page on choosing and setting up a local model (Ollama, LM Studio, or any
OpenAI-compatible server) for the Assistant: tool support, VRAM, permissions,
the measured results from the standard test in #4851, and troubleshooting
for the common failures.

* Guide: local models table with the full set of measured results
2026-10-26 06:49:46 +00:00
raykholo d7cc5621fa Troubleshooting: what to do when a design won't open (#5114)
Point to the `<file>.bak` copy every save keeps, the design.solidcraft in crash
reports, the recovery folder for unsaved designs, and how to attach the file
to an issue.
2026-10-26 06:49:44 +00:00
Matthew Knutson 816bcf10de Every new command comes with input and output schemas (#2829) (#4035)
* Every new command comes with input and output schemas

A test fails for a command without a contract or an output type, unless it is a planned!
placeholder or listed in schema/unconverted.txt, which holds the 400 commands that predate
the rule and only shrinks: the test also fails when a listed command has its schemas, when
the list names a command that doesn't exist, or when it isn't sorted.

`checked!` now takes only the command's id and checks the contract that id publishes
(`run_checked_as`), so the contract enforced is always the one described. Another test runs
every command that has a contract in strict with an unknown key, so a contract is never
published without `checked!`, under the command's own id, enforcing it.

A new book page, Command schemas, explains how to write a contract and a reply type, the
tests to add, the shared field types and how to add one, with a checklist; the failure
messages point to it. "Adding a command", CONTRIBUTING.md and AGENTS.md now include the
schemas, and the MCP tool description and docs/mcp.md no longer list the converted commands.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* List the commands added on main since: view.layout, view.history_layout, view.normal_to

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Command schemas guide: what participants and targets do, checking the evaluation, warning-free replies

- `participants` changes the result for some features and nothing for others; where it and
  another key override each other, or where it has no effect, say so in that key's
  description or in the summary.
- The example primitive's `targets` default to every body in the feature's component, not the
  bodies it meets; they're ignored when `participants` lists bodies.
- Check what each value does where the feature is evaluated, not only how the handler reads it.
- A reply test runs every form in Warn and checks for no `param_warnings` (Strict for a reply
  that isn't an object).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* List the 43 commands added on main since: frame, component, render and others

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* List the 11 commands added on main since: browser folding, standard views, parameter sweep, path flange

browser.collapse_all, browser.expand_all, inspect.parameter_sweep, sheet.path_flange and the
seven view.* standard views (back, bottom, front, isometric, left, right, top) landed on main
without schemas; the gate flags them, as intended. 457 commands listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Schema rule warns instead of failing for now

A command without input and output schemas prints a warning listing it
(test output and a CI annotation). SCHEMA_RULE_STRICT in coverage_tests.rs
(or SOLIDCRAFT_SCHEMA_RULE_STRICT=1) makes it fail again. Docs say "should,
and will become required".

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>
2026-10-26 01:43:02 +00:00
CamWight ab80a410a1 Viewport theme: a light 3D view in a dark window, and the reverse (#4146) (#4147)
* claim: #4146

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport theme: a light 3D view in a dark window, and the reverse (#4146)

Preferences › Interface › Theme gets a Viewport row under the theme tiles:
Follow theme (the default, as before), Light or Dark. Light or Dark draws
the 3D view in the built-in Light or Dark theme's colours whatever the
window uses, so a dark window can show the model on a light view and the
other way round; it works with every theme, theme files included.

The view takes the background and grid, bodies, selection and hover, the
origin, previews, sketches with their dimensions and glyphs, the navigation
cube and triads, box selection and the analysis colours. Panels, dialogs,
menus and the chips over the view keep the window's. Text and warnings drawn
straight onto the view (the sketch status line, the loose-end and midpoint
markers, the section's cut outlines, the 3D sketch tool and the sketch tools'
previews) take the view's side, so they read on its background.

theme::ViewportTheme is the setting (saved as `viewport_theme`: theme, light
or dark; an unknown saved value reads as Follow theme, as ColorVision's
does). Tokens::get() takes the view's colours from the other side only when
the view's dark or light differs from the window's; Tokens::view() is the
view's side for colours drawn onto it. The ground shadow follows the view
(the scene and highlight caches already key on the colours themselves),
ui.render draws in the view's colours, and ui.prefs takes `viewport_theme`
and reports `viewport_dark`. An older release ignores the setting and draws
everything in the theme.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport theme: follow the single set of axis colours

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>
2026-10-25 22:17:04 +00:00
leValdemar19 b94692497e Sketch Dimension: arc length from an arc and its end point (#754) (#4245)
* claim: #754

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27

* Sketch Dimension: arc length from an arc and its end point (#754)

The arc-length dimension existed (sketch.dimension type arc_length) but
the Dimension tool could only give an arc its radius. An arc and one of
its own end points, in either order, now dimension the arc's length:
click the arc, then its end. The pair was refused before ("cannot
dimension that combination"), so no existing result changes. A point
where the arc's end is (a coincident line end) counts as that end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SBN24y2TcqRcm2zn8Gkj27

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:17:01 +00:00
ChrisNo a3890b3bd5 Open a linked part from its assembly Browser row (#2348) (#2349)
* claim: #2348

* Open linked designs from assembly browser

* claim: #2348

* Test linked design action with multiple components selected

* Fix linked design lookup in browser storage and translate its menu action

* Add Turn Up Axis to complete UI catalogs

* Keep browser DXF download after linked-design checks
2026-10-25 21:15:48 +00:00
bherbruck 57d787f5ba Docs site: canonical links, robots.txt and sitemap.xml (#6888)
Search Console lists docs.solidcraft.io/, /introduction.html (the same page) and /guide/mcp.html as duplicates without a user-selected canonical. The Pages build now adds a canonical link to every book page (index.html and introduction.html both name /), skips the redirect stubs that already have one, marks 404 and the toc frame noindex, and writes a robots.txt pointing at a generated sitemap.xml.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:35:56 -04:00
HiroKenEz b1d4293cf0 One set of X, Y and Z colours for the origin axes, view triad and Move gizmo (#976) (#4430)
* claim: #976

* One set of X, Y and Z colours for every axis (#976)

The origin axes, the navigation cube's triad, 3D sketch triads and joint
origins drew `axis_x/y/z` (255,0,0 / 0,255,0 / 2,2,248), the view triad and
the headless picture drew `triad` (220,60,60 / 60,170,60 / 60,100,230) and
the Move gizmo drew `gizmo` (225,70,60 / 80,190,80 / 70,130,240). The
`triad` and `gizmo` tokens are gone: every axis reads `axis_x/y/z`, now the
view triad's values (the engine's `view::colors::AXIS_*`, so headless
pictures are unchanged, and the set the red-green table was measured for).
Older theme files' `triad` and `gizmo` lists still read and set the axes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:59:36 +00:00
raykholo 46f7d4d8dc Preferences: the web app's Updates section says how it updates (#5496) (#5500) 2026-10-25 19:29:04 +00:00
Kai Evans 0870d6474a Themes: Okibi (dark) and Akari (light), from the Okibi design system (#5730) (#5731)
* Themes: Okibi (dark) and Akari (light), from the Okibi design system

Two built-in themes mapped from the Okibi design system v1.8
(https://kaievns.github.io/okibi-akiri-design-system-spec/): cool charcoal
ash with a single ember accent, and its warm-paper light twin. Every one of
the 156 colours is set, so nothing falls back to the Dark or Light palette.

* Give the Okibi and Akari themes an icon palette

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 18:42:19 +00:00
Ahmad Khan 0f90fb7e8d Edge flange: material outside and tangent bend positions (#5941) (#6060)
* claim: #5938

* Edge flange: height measured from the outer sharp, the inner sharp or the bend tangent (#5938)

An edge flange feature takes height_from (outer, inner or tangent) in sheet.flange and Height From in the Flange dialog, saved only when it is not the outer sharp. A height shorter than the bend takes is refused with the smallest height that fits.

* Edge flange: material outside and tangent bend positions (#5941)

An edge flange's position can now be material_outside (the inner virtual sharp on the edge) or tangent (the bend's outer face tangent to the edge's face), in sheet.flange and the Flange dialog. The bend starting at the edge was already the outside position, which now also answers to bend_outside.
2026-10-25 16:25:18 +00:00
raykholo 4cd9a4f4d4 Web app: open a design from a link, and an embed mode (#6252) (#6253)
* claim: #6252

* Open hosted designs in the web app and add an embed layout

Fixes #6252.

Fetch HTTPS design links with bounded reads and open them as unsaved copies.
Keep model panels and joint controls in embeds without browser persistence,
application chrome, or unload prompts. Add a full-app link and iframe docs.

Cover query parsing, hostile URLs, size limits, and the compact layout.

* Translate the linked-design messages into Japanese and Chinese
2026-10-25 14:40:01 +00:00
Ahmad Khan 5a5a98ee80 Sheet metal: Path Flange, a chain of bends on an existing sheet edge (#4094) (#4096)
* claim: #4094

* Sheet metal: Path Flange, a chain of bends on an existing sheet edge (#4094)

Path Flange (`sheet.path_flange`): sketch an open chain of lines across a sheet edge, pick the
edge and one line; each line becomes a flange as long as the line and each corner a bend, one
feature and one body. The edge flange takes further bends (`then`), each on the far edge of the
one before, and the Flange dialog shows them as rows with a bin each.
2026-10-25 11:33:42 +00:00
fkirill 9d45fa6b34 Respect design units across length fields (#3271) (#3315)
* claim: #3271

* fix(ui): use document units across length fields (#3271)

* Format

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Adapt drag-extrude and joint tests to document-unit length helpers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ui): preserve blank parameter drafts and cover bounded lengths

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 10:24:30 +00:00
Jesper We bbc3aa4301 Offset Plane: through a point (#3765)
* claim: Offset Plane through a point

* Offset Plane: through a point instead of at a distance
2026-10-25 06:02:14 +00:00
Ahmad Khan a49698ee3d Value fields step with the arrow keys and the mouse wheel (#2320) (#2608)
* claim: #2320

* Value fields step with the arrow keys and the mouse wheel (#2320)

Up/Down in a focused value field step a plain number by 1 in the unit it is written in
("20 mm" -> "21 mm", "2,5 mm" -> "3,5 mm"), 10 with Shift and 0.1 with Alt; the wheel over
the focused field does the same, a step a notch, instead of scrolling the panel under it.
Expressions such as `width / 2` are left as typed, and an open completion list keeps the
arrows. Every expression field gets this through `params_dialog::complete`: the value box
beside the geometry in every dialog, the dialog rows, sketch dimensions, the dimension
editor, Drive Joint and the Parameters window.

* The value box beside the geometry stays under the pointer

The box rides on the arrow's tip, so each wheel notch that stepped its value moved it, and
after a notch or two the box slid out from under the pointer and the wheel zoomed the view
instead. While the pointer is over it the box now keeps its place, and it goes back to the tip
when the pointer leaves.

* Value stepping is a preference, off by default

Preferences › General › "Step values with the arrow keys and the wheel" (`step_values`,
also through `ui.prefs`) turns on Up/Down and wheel stepping in value fields, and the value
box staying under the pointer while the wheel steps it. Off, as by default, value fields
behave as before: Up/Down move the text cursor, the wheel scrolls, and the box beside the
geometry follows its arrow.

* Value stepping test: Bend Radius is under Advanced now

* Value stepping test: the preference is on the Design page

* Value stepping test: draw the field straight into the test's frame (#2320)

The shared outer-host rule now covers tests too; the field needs no panel around it.
2026-10-25 05:34:03 +00:00
Ahmad Khan 919f54a23e Sheet metal: bend angles as the included (press-brake) angle, set per rule (#4968) (#6040)
* claim: #4968

* Sheet metal rules: bend angles as the included angle (#4968)

A sheet metal rule's angle_display (bend, the default, or included) sets
how the DXF bend notes read each bend: a 58° bend is B1 UP 122° R1 under
an included rule. The bend table always gives included_angle_deg beside
angle_deg (CSV and JSON). Set in sheet.manage_rules and the Sheet Metal
Rules dialog, listed by sheet.rules; the flange angle stays the bend angle.

* Bend table: the included angle as the CSV's last column, so the columns before it keep their places (#4968)

A test also covers a body whose base flange names its rule, which keeps that rule's notes whichever rule is active, and a bend down.

* Sheet Metal Rules: Angle Display reads Bend or Included, so the choice fits its box (#4968)

The rule editor's row contract lists the new row and the key the dialog sends.

---------

Co-authored-by: begsaquib <saquibbeg4444@gmail.com>
2026-10-25 05:11:13 +00:00
Matt Van Horn 43565cd33b Snap drawing to where two sketch curves cross (#4967) (#5342)
While drawing, the pointer snaps to the crossing of two lines, circles or
arcs and keeps the new point on both curves. Shift still prefers a midpoint.
2026-10-25 04:50:20 +00:00
rbousigues88 157c0d68d0 Sketch Dimension: point/circle to circle or arc edge (#4064) (#4065)
* Sketch Dimension: point or circle to the edge of a circle or arc (#4064)

Adds PointCircleDistance and CircleCircleDistance (edge-based, like #1863):
solver, constructive seed, drawing, command wiring, tests, UI scenarios and docs.
Saved designs gain two constraint kinds, in one pending format file.

Fixes #4064.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch Dimension scenario: pick the circle at its top edge (#4064)

The bottom edge sits under the Dimension tool's status hint in the scenario window, so the second click never reached the tool.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-25 04:29:28 +00:00
raykholo 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.
2026-10-25 03:34:20 +00:00
raykholo 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.
2026-10-25 03:19:34 +00:00
Colin Faulkingham 1f93b6d39c Joint: use the picked straight edge as the motion axis (#5457) (#6086)
* claim: #5457

* fix: align new straight-edge joint snaps with the edge (#5457)
2026-10-25 01:25:14 +00:00
semmelrinde 9d226b3535 Sheet metal DXF: options to leave out the bend lines and to use one layer (#4314) (#6130)
sheet.export_dxf always wrote the dashed bend lines (BEND_UP,
BEND_DOWN) and split the flat pattern over a layer for each role. A
cutter that cuts every line it is given cut the bend lines too.
bend_lines: false now leaves them out, and layers: single writes every
entity on layer 0. The defaults keep today's output.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 01:25:02 +00:00
NOWN-DEV c0640cb95e inspect.parameter_sweep: check a design across a parameter's range (#2273) (#2276)
* inspect.parameter_sweep: check a design across a parameter's range

Rebuilds a copy of the design at each value of a parameter and reports what
no longer holds compared with the design as it is: features that fail or
warn, joint origins that are lost or turn over on their own part, sketch
planes that turn over, and sketch solves that fail or fold. The design is
not changed; each value's rebuild time is reported too.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.parameter_sweep: params doc matches what it takes and returns

`steps` takes at most 199 (200 values), as the check says; failed and
warned entries carry the feature's name; a lost origin says which one; a
value the parameter refuses gives {value, ms, ok: false, error}. The
refusal test covers steps 200.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* inspect.parameter_sweep: Japanese and Chinese labels

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Aac7mP9rthK3WsPpPRvCsF

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-25 01:19:00 +00:00
fugazister 4b119dd471 Performance overlay: frame-time graph and histogram, triangles, draw calls (#2548) (#2549)
* Performance overlay: frame-time graph and histogram, triangles, draw calls (#2548)

Net change of the perf-overlay branch, rebased onto main as one commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Document frame_rate_details in ui.prefs

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>
2026-10-24 22:50:43 +00:00