#13316, #13338, #13384 and #13407 landed on main as squashed commits. This merge keeps only this branch's own changes on top of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)
A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the widget registry is a shared crate, ui-registry (#12936)
The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)
TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: Preferences search is a kit module, settings_search (#13256)
prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the kit owns the menu entry and the list width (#13257)
Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)
parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)
The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)
LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)
continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: length fields take unit scaling and length parsing from the host (#13270)
The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)
geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Material editor: the Remove buttons are translated (material.editor.remove)
The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)
bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)
no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Theme: the icon palette leaves Tokens; the icon renderer owns it (#13304)
A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Shared UI crates: the token contract as a shared crate, solidcraft-theme (#13304)
Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.
The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: the icon lint and the docs read palettes.rs at crates/theme (#13304)
lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: format the field contrast test (#13304)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* MCP: the server machinery is a shared crate, solidcraft-mcp-server (#12947)
JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* MCP: the shared server tests what it owns, on its own (#12947)
Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: the architecture table lists the shared crates and the MCP split (#12947)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control: the token files and Effect are a shared crate, solidcraft-control (#12946)
The per-launch token, its files, the discovery file and the client's token
lookup move from the engine to crates/control, with no engine dependency.
Which app they belong to (name, data folder, environment, UTC format,
default port) is an `App` value; SolidCraft's is engine::control_auth::APP.
`Effect` moves with them; which requests do what stays in the engine's
control_pending. Callers switch paths; files and messages are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Control: the loopback control server is shared; SolidCraft gives it its Protocol (#12946)
The token-admitted JSON-lines server (framing and line limit, connection
cap, control.running/pending/capabilities, busy and timeout replies,
control.subscribe events), ControlRequest and the design watch move to
solidcraft-control as `server::Hub`. What a request means (its name in
messages, its effect, command id, step count, the batch wait, the
capabilities) is the app's `server::Protocol`; SolidCraft's is in
apps/solidcraft/src/control_server.rs with its listeners and token files.
The wire format is unchanged and the server's tests move with it.
Also from review: Effect::when_done and the timeout text take the app's
word for its document ("design" for SolidCraft), the Listen docs name no
SolidCraft preference, and bench_assistant.rs is formatted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Control: the shared server speaks the app's words and listens on loopback only (#12946)
The change event, the revision method, the docs path in the unauthorized
reply and the document noun come from the app's Protocol; SolidCraft's
replies are byte-identical. The design watch is the DocumentWatch.
Hub::listen refuses a listener off loopback, so no app can put the
channel on the network; SolidCraft binds 127.0.0.1 as before.
control_pending.rs is formatted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Control: ui.screenshot and ui.resize are shared over egui (#12946)
The window's screenshots in flight (the frame waits, the AO-settled
shot, the hidden-window and chat-turn refusals, the 8 s deadline) move
from SolveApp to solidcraft_control::screenshot::Shots, with the PNG
encoding; ui.resize and the hidden-window test go to window. SolidCraft
passes its AO settle, its issue report's frames and its vfs write, so a
second app can serve ui.screenshot with the shared crate alone. The issue
report and tests reach the queue as app.shots; replies are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control: ui.screenshot and ui.resize are their own shared crate, solidcraft-control-egui (#12946)
d8d35d454 made egui and image dependencies of solidcraft-control, so the
headless engine, the CLI and the MCP bridge compiled egui. The screenshot
and window modules move unchanged to solidcraft-control-egui; ui-egui
uses them from there. cargo tree finds no egui under engine, mcp or control.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control: the app gives the screenshot reply for no frame (#12946)
The shared text named SolidCraft's ui.render. Shots::new takes the app's
words; SolidCraft passes its reply unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control: tests for the screenshot gate, a hidden window and frames nobody waits for (#12946)
Shots::issue takes a frame on the third frame and 150 ms after the request,
or 500 ms later for a view that keeps moving; a hidden window refuses a
request and answers one waiting; with nothing pending the frames that came
are dropped. Each fails on a one-line mutation of screenshot.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12627
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Surface edits: input and output schemas for Stitch, Unstitch, Reverse Normal, Thicken, Offset Surface, Untrim and Fair Surface (#12627)
Typed contracts for surface.stitch, surface.unstitch,
surface.reverse_normal, surface.thicken, surface.offset, surface.untrim
and surface.fair in a new schema/surface.rs, wired with checked!,
.contract and .output; the seven leave schema/unconverted.txt. Each reply
is the feature commands' FeatureResult.
Each key reads as its handler reads it, with its message: bodies and
targets through NameList, face points through FacePoints and
OffsetFaces, Untrim's face and holes through UntrimFace and UntrimHoles,
Fair's strength and iterations with their range messages, lengths as
LengthExpr, and the common feature keys through FeatureCommon. `bodies`,
`faces` (Offset) and `face` (Untrim) are optional in the types and
required in the schemas, so a call without them keeps the handler's
message. Thicken takes `touching`, as the other joining features do.
The contracts refuse keys of the wrong type that the handlers ignore
today: an item of `faces` that isn't a point (skipped today), a `faces`
that isn't a list, and flags, tolerances, strength and iterations of the
wrong type.
Tests in schema/surface_tests.rs: the keys, 46 calls that keep their
message in strict, 15 stricter keys, 22 calls checked against both schema
and handler, and each command's reply against FeatureResult.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Surface edits: the contracts refuse a null or empty `bodies`, and a null `faces` or `face`
`bodies` (Stitch, Thicken), `faces` (Offset Surface) and `face` (Untrim)
are optional in the types so that a call without them gets the
handler's own message. A `null` or empty value also reached the handler,
so the check (warn, strict, MCP validation, batch preflight) passed
calls the handler then refused. The readers now refuse them with the
handler's message: `surface_bodies` maps `null` to no names, as
params::string_list does, and refuses an empty list; `faces` and `face`
take `present`, so a `null` goes to OffsetFaces and UntrimFace, which
refuse it. A key left out still goes on to the handler.
Raised by CodeRabbit on #12628.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Claim #12852: motion analysis schemas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Schemas for Plot Motion Study and Motion Interference
The two motion analysis commands (motion.plot, motion.interference)
get input contracts and output schemas, so describe_command gives
their exact parameters and replies. The contracts live in
schema/motion_analysis.rs and read each key as the handlers read it,
with their messages. The handlers build their replies from the typed
structs, and the JSON doesn't change.
Fixes#12852, part of #2829.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Motion Interference: read the part lists with the handler's messages
`occurrences` and `against` were `Vec<PartRef>`, so a value that isn't a
list got serde's "expected a sequence" in strict instead of the handler's
"`occurrences` must list occurrences", and the schema didn't say a list
holds at most 10000. They are now `PartList`, which reads a list as
`cmd/motion.rs`'s `occurrences` does, with its messages and its limit.
`OccurrenceKeys`, the contact sets' list, shares the same reader.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: tap markings search only nearby bodies again, by exact boxes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Threads: a tap marking looks for its cylinder only in the bodies near it again
A tap marking (a Thread feature, or a tapped hole's marking) looked for the
cylinder at its point in every body of the design, and each search meshes
the body afresh. #10303 passed over bodies whose mesh box lay out of the
search's reach, but the SMLib merge (#11351) left that mesh-based version
out, and its two cost tests were listed as known failures until an exact
replacement came. This is it: a body is passed over when its exact box
(the one holes and cuts already use) lies farther from the point than the
search looks, a twentieth of the body's size plus twice its mesh's
tolerance. The two tests come off tests/KNOWN_FAILING_TESTS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Claim #12836: render result command schemas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Agents and scripts: the render job commands describe their parameters and replies
Render Progress, Cancel Render, Retain Displayed Render Result, Regrade
Linear Master, Copy Render Image and Export Render Image get input and
output schemas (#12836, part of #2829), in the new schema/render_jobs.rs.
The handlers parse their parameters with serde; each key in the
contracts reads as the same Rust type and passes serde's message on
whole (`as_handler`), so strict keeps today's messages. A job is the
render crate's JobId (`Job`, with a hand-written schema), and Regrade's
presentation its Presentation. The replies are typed: a job's status
keeps its id, progress (phase as an enum), error and frame typed, and
the render crate's other sections as JSON objects; the handlers build
them through `retyped`, and the JSON doesn't change. The shot queue is
RenderQueueState, which now derives its schema, and Export's format
moves to the schema module. Five lines leave unconverted.txt; Copy
Render Image had no line but no schemas either.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render export: type the reply before writing the file
`render.export` converted the frame, linear master and presentation
information to their reply types after it had replaced the file. A
conversion that failed then returned an error with the file already
written. The conversions now run first, so a failure leaves no file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Claim #12844: Sweep Joint schemas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Schemas for Sweep Joint (joint.sweep)
Sweep Joint gets an input contract and an output schema, so
describe_command gives its exact parameters and reply. The contract
lives in schema/joint_sweep.rs and reads each key as the handler
reads it, with the handler's messages. The handler builds its reply
from SweepResult, and the JSON doesn't change.
Fixes#12844, part of #2829.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Claim #12884: Chamfer schemas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Chamfer: input and output schemas for solid.chamfer (#12884)
Adds ChamferParams, the contract for solid.chamfer, and replies with the
shared FeatureResult. A misspelt key is now named in warn and refused in
strict; calls that work today get the same reply.
Two tests used Chamfer as their example of a command without a contract;
they now take the first such command from the registry instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Chamfer schemas: format
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Brennen Herbruck <brennen.herbruck@gmail.com>
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)
A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the widget registry is a shared crate, ui-registry (#12936)
The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)
TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: Preferences search is a kit module, settings_search (#13256)
prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the kit owns the menu entry and the list width (#13257)
Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)
parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)
The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)
LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)
continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: length fields take unit scaling and length parsing from the host (#13270)
The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)
geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Material editor: the Remove buttons are translated (material.editor.remove)
The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)
bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)
no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Theme: the icon palette leaves Tokens; the icon renderer owns it (#13304)
A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Shared UI crates: the token contract as a shared crate, solidcraft-theme (#13304)
Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.
The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: the icon lint and the docs read palettes.rs at crates/theme (#13304)
lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: format the field contrast test (#13304)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
---------
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Since about 4:30 AM ET GitHub answers 504 for sccache's x86_64 release
asset, so every job that sets up the compiler cache fails before it builds,
including merge-queue runs. The setup step may now fail; the job then clears
RUSTC_WRAPPER and builds uncached, and the stats steps skip.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)
A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the widget registry is a shared crate, ui-registry (#12936)
The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)
TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: Preferences search is a kit module, settings_search (#13256)
prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the kit owns the menu entry and the list width (#13257)
Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)
parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)
The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)
LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)
continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: length fields take unit scaling and length parsing from the host (#13270)
The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)
geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Material editor: the Remove buttons are translated (material.editor.remove)
The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)
bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)
no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MCP: the server machinery is a shared crate, solidcraft-mcp-server (#12947)
JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* MCP: the shared server tests what it owns, on its own (#12947)
Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: the architecture table lists the shared crates and the MCP split (#12947)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Layers: a Shared crate class, and cargo xtask shared builds each one on its own (#12935)
A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the widget registry is a shared crate, ui-registry (#12936)
The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Theme: the tab accent takes the tab, the sketch and the tool as data (#12938)
TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: Preferences search is a kit module, settings_search (#13256)
prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: the kit owns the menu entry and the list width (#13257)
Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: number parsing and recent colours in the kit, expression support passed in by the host (#13258)
parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: navigation-bar values and the horizontal wheel in the kit (#13259)
The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: layout mode and card geometry in the kit, input shield set by the host (#13260)
LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: continuity rows leave the kit, the navigator figure takes its axes as input (#13261)
continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* UI kit: length fields take unit scaling and length parsing from the host (#13270)
The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Colour: the sRGB rule and the analysis palette are a shared crate, solidcraft-color (#13291)
geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Material editor: the Remove buttons are translated (material.editor.remove)
The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Shared UI crates: ui-text, the message catalogs' machinery as a shared crate (#13290)
bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* i18n tests: the painted-English sweeps install the catalogs themselves (#13290)
no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Model cache key: the colour crate is one of the crates a model is computed from (#13291)
render and doc build on solidcraft-color since the sRGB rule moved there, so its source decides
the saved model as much as theirs does. EVALUATION_CRATES in crates/io/build.rs lacked it, which
the_cache_key_covers_every_crate_the_model_is_computed_from caught: a change to the colour crate
would not have invalidated a cached model.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui-egui tests: each test that reads a translation installs the catalogs itself (#13290)
Lookups read only catalogs installed with i18n::install, which the app does at startup and
SolveApp::new and fonts::test_context do for the tests that use them. 55 tests read a message
before (or without) either of those, so they passed only when another test in the same process
had installed the catalogs first; run alone, as nextest runs each test, every id read as itself
("ui.agent_chat_model_no_tools", "Cam" where "Nocken" was expected). Each now calls
crate::i18n::install() first; a language scope entered before it would otherwise keep the
uninstalled English.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The list grows with the menu, so it follows the repo's rule for growing lists (cargo xtask lists).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bar's own menus take their item's enabled flag. replace() refuses a menu of the bar
(it keeps the system entries the layout gave it), now documented and tested.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent Chat sends ui.agent_chat {toggle}, which the control channel now takes (show or hide as
the panel's button does). ui.help runs every item the Help menu sends (docs, messages and
install_cli too) and answers an error for an unknown item instead of a silent ok. A test
runs the menu's control ids through the control channel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Quit is app.quit, Settings and Keyboard Shortcuts are ui.prefs {open}, Agent Chat is
ui.agent_chat and Help's items are ui.help {item}; the menus are menu.file, menu.file.recent,
menu.edit, menu.window and menu.help. The id round trip and the shortcut table are tested
without a window on every system; the module builds there for its tests only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MenuTable lays out the whole bar (the app menu's system entries, the Window menu's extras,
submenus) and replaces a submenu's entries itself; NativeMenu only turns rows into AppKit
items. Nothing is dead code off macOS, and the layout and replace tests run everywhere.
The Window menu's id is menu.window; menu ids are never dispatched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
app_bar drew its own drag area, traffic-light inset and caption buttons beside the shared
TitleBar; it now runs TitleBar::show and answers the returned WindowAction through
titlebar::run, so the title bar tests pin what SolidCraft draws.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
window_chrome::native_menu builds the menu bar from ui_kit::Item trees (the app menu's system
layout, the system's Window entries) and returns the chosen entry as (id, params); the app-menu
title comes from the app. SolidCraft's native_menu.rs builds its Items from FileAction with
command ids or ui.* ids and maps the ids back, with the same entries, shortcuts and labels.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
window_chrome holds what any Craft app's window needs without SolveApp: viewport(title, app_id,
custom_titlebar) with SolidCraft's window settings, TitleBar (traffic-light inset, drag area,
caption buttons) that returns a WindowAction for the app to run, and the resize zones and hit
test. titlebar.rs is SolidCraft's thin use of it: Close asks about unsaved designs first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
Shots::issue takes a frame on the third frame and 150 ms after the request,
or 500 ms later for a view that keeps moving; a hidden window refuses a
request and answers one waiting; with nothing pending the frames that came
are dropped. Each fails on a one-line mutation of screenshot.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The shared text named SolidCraft's ui.render. Shots::new takes the app's
words; SolidCraft passes its reply unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d8d35d454 made egui and image dependencies of solidcraft-control, so the
headless engine, the CLI and the MCP bridge compiled egui. The screenshot
and window modules move unchanged to solidcraft-control-egui; ui-egui
uses them from there. cargo tree finds no egui under engine, mcp or control.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The window's screenshots in flight (the frame waits, the AO-settled
shot, the hidden-window and chat-turn refusals, the 8 s deadline) move
from SolveApp to solidcraft_control::screenshot::Shots, with the PNG
encoding; ui.resize and the hidden-window test go to window. SolidCraft
passes its AO settle, its issue report's frames and its vfs write, so a
second app can serve ui.screenshot with the shared crate alone. The issue
report and tests reach the queue as app.shots; replies are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The change event, the revision method, the docs path in the unauthorized
reply and the document noun come from the app's Protocol; SolidCraft's
replies are byte-identical. The design watch is the DocumentWatch.
Hub::listen refuses a listener off loopback, so no app can put the
channel on the network; SolidCraft binds 127.0.0.1 as before.
control_pending.rs is formatted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
The token-admitted JSON-lines server (framing and line limit, connection
cap, control.running/pending/capabilities, busy and timeout replies,
control.subscribe events), ControlRequest and the design watch move to
solidcraft-control as `server::Hub`. What a request means (its name in
messages, its effect, command id, step count, the batch wait, the
capabilities) is the app's `server::Protocol`; SolidCraft's is in
apps/solidcraft/src/control_server.rs with its listeners and token files.
The wire format is unchanged and the server's tests move with it.
Also from review: Effect::when_done and the timeout text take the app's
word for its document ("design" for SolidCraft), the Listen docs name no
SolidCraft preference, and bench_assistant.rs is formatted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
The per-launch token, its files, the discovery file and the client's token
lookup move from the engine to crates/control, with no engine dependency.
Which app they belong to (name, data folder, environment, UTC format,
default port) is an `App` value; SolidCraft's is engine::control_auth::APP.
`Effect` moves with them; which requests do what stays in the engine's
control_pending. Callers switch paths; files and messages are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
Batches (mixed, notifications only, the 1000 limit), an unasked response,
the protocol version fallback, null arguments, enum mismatches, the empty
default lists and the list-then-call order Tools::call relies on are tested
in solidcraft-mcp-server with a fake Tools; the protocol-only lines move out
of SolidCraft's wrapper tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JSON-RPC over stdio, the MCP lifecycle, tool and resource methods, tool
definitions with their annotations, the argument check and the text and
image results move to crates/mcp-server, which has no engine or GPU
dependency and is classed Shared. SolidCraft's tools, instructions,
resources and the agent's folders stay in crates/mcp and register into it
through the Tools trait; its public API, tools list and replies are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
mesh.convert gets MeshConvertParams {body?, tolerance?, name?, participants?}
in a new schema/mesh.rs and replies with FeatureResult. body is text (blank
or null converts the selected body, as today); tolerance is a LengthExpr whose
range stays the kernel's to check; body_name and body_names are left out
because the solid keeps the mesh body's name.
The imported_box test helper in tests/mesh_convert.rs is now pub(crate) so the
schema tests convert the same box.
Co-authored-by: Claude <noreply@anthropic.com>
lint_icons.py compared the icon review page with assets/styles/ui/palettes.rs, which moved into
solidcraft-theme; it reads crates/theme/src/palettes.rs now (cargo xtask icons passes again), and
the READMEs, docs and test comments name the new path. set_custom and scoped_tokens say they set
tokens only, so a host with an icon renderer sets its palette too; the field contrast test applies
each built-in theme with set_file_palette, as the app does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
Tokens with SolidCraft's light and dark values in code (palettes.rs moves in from
assets/styles/ui, with the eight primary strokes it shared with the icon artwork written as
icon_strokes), colour vision, TabAccent, contrast checks, apply/visuals and the per-thread and
per-context theme state move into crates/theme, class Shared. It holds no data files and does not
depend on theme files or icon roles; with nothing set, components draw with the built-in tokens.
The theme system stays in ui-egui's theme module: the custom-theme glue (prepared theme, tab
targets and their fade, blur intent), publish_tab (was TabAccent::publish, which now stores its
result through TabAccent::set_current and set_published), set_file_palette, ViewportTheme.
ui-egui's build script no longer generates the primary-stroke constants; an icon test holds the
crate's literals equal to palette.toml's. Call sites use solidcraft_theme paths with no
re-export (one rename pass). Tokens, theme files and rendered pixels are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
A theme file's icon palette was a field of Tokens, so the token set depended on the icon roles
ui-egui's build script generates. It is now the icon renderer's per-thread state
(icon_svg::set_theme_palette), set alongside the theme's palette by theme::set_file_palette, and
theme_files resolves it with resolve_icon_palette (the same overlay merge as the tokens) and
exports it as an argument. Rendered pixels, theme files and exports are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
no_english_is_painted_where_the_language_has_a_translation and
the_sweep_reports_english_only_where_a_translation_exists read translations without installing the
app's catalogs, so they passed only when another test in the same process had installed them first
(run alone, every id read as itself).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bidi, Catalog, Locale, lookup, formatting, labels and language choice move from ui-egui's
i18n into solidcraft-ui-text, a Shared crate any front end can use. The host installs its
catalogs once; with none installed every id reads as itself. SolidCraft's 35 catalogs, the
build script that merges them, and the adapters that read engine types (diagnostic,
tr_engine_error, and the given-name and browser-label vocabulary as the LocaleNames trait)
stay in the app's i18n. The app installs its catalogs when it starts, when an app is built
and in the widget test context. Call sites use the crate's path, with no re-export at the
old one; messages, ids and fallbacks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
The editor's two Remove buttons called tr("Remove"), which looks up an id, so they read
"Remove" in every language. They use a new id, material.editor.remove, whose text in each
locale is the one that locale already gives the plain Remove button
prefs.network_host_remove (33 of 35 catalogs have it; the other two show English, as now).
The label-literal guard found it once the file's import named tr directly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
geom's srgb module (the one sRGB transfer function every encode and decode goes through)
moves whole into solidcraft-color with its tests, and so does AnalysisColors, the analysis
palette the theme fills and the renderer reads. render, doc, io, engine, gpu and ui-egui use
the crate; there is no re-export at the old paths. The theme's unused material_color wrapper
is removed, so no non-test theme code names solidcraft_engine. Values are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
The kit's length field converted millimetres to and from the design's units through three
prefs helpers. Those are the host's unit rules, so Expressions gains unit_scale,
length_number and parse_length_mm, and length_drag reads them from the context.
DocExpressions implements them with the same prefs helpers, so the app is unchanged. With
nothing installed the field edits plain millimetres. No kit file calls prefs outside
tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
continuity_row and its explained and tip variants are CAD controls over the kernel's
Continuity: they move beside their callers in dialogs_surface, still drawn with the kit's
choice_row. The Preferences navigator figure takes its axes (screen direction and depth)
as NavigatorAxis input; SolidCraft computes them from its default camera. dashed_line is
painter geometry and moves into ui_kit::dash with its test. No kit file uses the engine,
the record or the viewport outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
LayoutMode (with its per-context publish and current), the card gap and the panic-free
clamp move into the kit's containers and panels, and Cards into stages. Cards::new takes
the workspace width instead of reading the ribbon's constant. The flag that says a report
covers the 3D view is now the kit's (input_blocked, set_input_blocked); the app's CadInput
guard sets it, and issue_report's own copy is gone. Serde names are unchanged, so saved
preferences load as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
The bar's edge, size, idle and button-kind values move beside the bar's drawing in
ui_kit::navigation as NavigationEdge, NavigationSize, NavigationIdle and NavigationKind (the
kit already has a container Edge). Serde names are unchanged, so saved preferences load as
before. take_horizontal_wheel moves into the theme picker, its only caller. SolidCraft's
slot table stays in navigation_bar.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
parse_number (decimal point or comma, Unicode minus) moves from prefs into the kit's
fields, and recent_colors moves whole into ui_kit::recent_colors beside the colour picker.
The kit no longer knows the expression language: a host installs an Expressions (is this
a literal, is this a unit, complete or step this text) per egui context, and fields read
it for label drags and completion. SolidCraft installs DocExpressions, built on doc::expr
and params_dialog::complete, when the app first draws into a context. Behaviour in the app
is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
Item (a command or ui.* action with its label, shortcut, icon, params, children, swatch
and tip) and LIST_W move from context_menu into ui_kit::menu, with the same serde form, so
the control channel's menu report is byte-identical. context_menu keeps building
SolidCraft's entries (cmd, and act over Item::action) and returns kit Items. No kit file
refers to context_menu any more.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
prefs_search moves whole into ui_kit::settings_search: it filters any settings page the
kit draws, and SolidCraft's Preferences window is one use of it. It publishes its handles
into the widget registry and reads the same wall clock as before (web_time), so no kit
file calls the app for search any more. Every caller uses the new path; state ids, handle
names, matches and scores are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
TabAccent::publish takes the open tab, whether a sketch is being edited and whether a tool
or dialog is active, instead of SolveApp; SolveApp::publish_tab_accent works the three out
in one place. scoped_panel_tokens is public, so a host can draw any component in another
palette or accent. The theme and its tests no longer name SolveApp, Services or workspace.
Accents are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
The handle, rect and count registry that UI tests, scenarios and the control channel read
moves out of scenario into solidcraft-ui-registry, the first Shared crate. The kit publishes
into it directly; scenario re-exports it, so scenarios, scripts and the rest of ui-egui are
unchanged and every handle keeps its name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
A Shared crate depends only on other Shared crates, solidcore and external crates (UI
toolkits included); any layered or testkit crate may use one. The layer check reports a
Shared crate's dependency on an app crate, of any kind. cargo xtask shared checks each
Shared crate's manifest (workspace lints, licence, README) and that no include_str! or
include_bytes! reaches outside it, then builds and tests it with its Shared dependencies in
a scratch workspace of its own. cargo xtask ci runs it after the layer check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LUXeKQRurMYJST9eSZWKzW
* Agents and scripts: Select and Clear Selection describe their exact parameters and reply (#13132)
select.set and select.clear now publish typed input and output schemas.
Each item of select.set is read as the handler reads it today, with the
same error messages, and both replies are built from the typed value and
serialize to the same JSON as before. Their lines leave unconverted.txt.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* Select: the item test matches each item to the branch of its type
`conforms` doesn't check `const`, so every_selection_kind_is_taken now also
finds the SelectItem branch whose `type` const is the item's and checks the
item against it, and checks that every branch is tried.
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
* claim: #13142
* Sketch: a preference to project references as construction geometry (#13142)
Project, Intersect, Include 3D Geometry and the face edges a new sketch on a
face projects all came in as normal geometry, so they joined the sketch's
profiles even when they were only meant as references to draw against.
A new preference, "Project references as construction geometry" (Preferences >
Sketch, off by default), makes them start as construction geometry. They stay
linked to the model. It is the user's, like "Project face edges into new
sketches": it is kept when another design is opened or started, follows the
user across tabs and is saved with the preferences. sketch.project,
sketch.intersect and sketch.include_3d take an optional `construction`
boolean so scripts and agents can choose per call.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #13196
* Schemas: the rectangle and polygon sketch commands describe their parameters and replies (#13196)
The seven commands sketch.rectangle.two_point, three_point, center, rounded
and sketch.polygon.inscribed, circumscribed, edge get a typed input contract
and an output schema; their replies are built from the reply types and their
lines leave schema/unconverted.txt. A test that passed the rectangle an `ids`
key it never read no longer does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11705
* Press Pull: the dialog the Q shortcut opens is titled PRESS PULL, not EXTRUDE (#11705)
Pressing Q (or picking Press Pull) with a planar face, a profile or
nothing selected opened the Extrude panel, so the shortcut and the
window disagreed. The panel now says PRESS PULL whenever it was opened
by Press Pull; a plain Extrude (E) still says EXTRUDE.
- lib.rs: the extrude dialog opened by solid.press_pull always carries
the press_pull marker now (before, only with an empty selection)
- dialogs.rs: heading and issue-report title use PRESS PULL for it
- new ui.heading_press_pull message in assets/locales/pending/
- regression test in preselect_tests.rs + UI scenario pressing Q and E
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
* Address review on #11712: Press Pull header, attribution row, precise test assertions
- dialogs.rs: the panel body header of a Press Pull extrude uses the
solid.press_pull command (icon and label) instead of Extrude's
- attribution/pending/press-pull-dialog-title.md: row for the pending
locale asset
- preselect_tests.rs: assert the drawn header by accessibility role and
place in the dialog header band, for Q and for plain E
- scenario: check the press_pull flag and the painted header label
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
* Fix f64/f32 in the header-label test assertion
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
* Pin the header-label test to the dialog header band (no role filter)
The accesskit tree carries the painted title under its own node, not
Role::Label; the header band rect is the precise anchor, so a
same-named toolbar widget cannot satisfy the assertion.
Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
* Press Pull title test: read the drawn header from the node value as well as its label
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: fork <fork@foo.com>
Co-authored-by: Mistral Vibe <vibe@mistral.ai>
Co-authored-by: Brennen Herbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #13138
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Shaders keep LF line ends on every checkout (#13138)
Tests embed .wgsl files with include_str! and match multi-line excerpts
written as Rust strings, which are always LF. On a Windows checkout with
core.autocrlf=true the shaders came out CRLF, so
gpu_volume_signed_primary_preserves_old_perspective_lens_none_and_zero_bytes
never matched. Mark *.wgsl as text with eol=lf, like requested_icons.txt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add Hole command guide (machinist perspective)
User guide for the Hole command: locating holes from two edges, UNC/UNF tapped holes, tap drill readout, blind tapped holes with a separate thread depth, and drill point angles. Tested in SolidCraft v0.30.1.
* Address review: reference edges, curved faces, multiple holes, presets
Updates from the CodeRabbit review, checked against the Hole dialog in SolidCraft v0.30.1:
- Reference edges must be straight and not parallel (no opposite sides).
- Single holes can go on curved faces; edge references need a flat face.
- Multiple placement: sketch points, set Placement to Multiple, select the points.
- Tapped holes carry the thread as a callout; the thread is not cut into the wall. Points to the Thread command for real thread geometry. No Modeled control appears in the v0.30.1 Hole dialog.
- Added a row for Personal Presets (Save new, Rename, Delete).
* Render preview: near-black colours decode as the exported image does
The product-lights branch of the viewport shader decoded the base colour
with `to_linear`, whose dark branch returns `c` instead of `c / 12.92`,
and on a linear attachment sent its encoded result back through
`out_color`, which decoded it the same way. Colours at or below about
#0a0a0a therefore lit far brighter in the Render preview than in the CPU
image (#050505: 38/255 against 5/255).
The branch now decodes with a piecewise `srgb_to_linear`, the transfer
`job::image` uses, returns linear light directly to a linear attachment
and encodes once otherwise. `to_linear` and the Design viewport's path
are unchanged.
The test-only offscreen renderer takes the preview lights, and a new test
renders a sphere under one uniform light for black, near-blacks on both
sides of the 0.04045 knee and mid-tones, on linear and sRGB attachments,
against the CPU image's decode, tone map and encode.
* Render preview: decode with the shader's existing srgb_decode
The product-lights branch now calls `srgb_decode`, the exact piecewise
decoder the sky fill already uses, instead of an identical new
`srgb_to_linear`.
A reference picked while a face was split keeps the piece's qualifier
(F4:side:a1#[F4:start]). When that face is one face again, the whole face
still bounded by the faces the qualifier names now matches as the piece, so
the projected edge between it and F4:start is found. Numbered pieces stay
strict.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Add failing test: pattern of an untargeted cut with another component active (#12996)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Pattern of an untargeted cut or intersect works on its source's component (#12996)
The copies took the bodies of the component active when the pattern was made,
so with another component active the pattern said there was no body to cut.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Pattern: source-component scope only when the pattern's component has no body; also for misses and sheet cuts (#12996)
- The copies of an untargeted cut or intersect take the source's component only when the
pattern's own component has no body (and no participants): a pattern that cut bodies of its own
component before still does, so saved designs rebuild the same.
- That scope is chosen before the copy-miss count and the sheet-cut step, so all three use it.
- Test: pattern_of_an_untargeted_cut_still_cuts_its_own_components_body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
timeline.group, ungroup, move_to_group, rename_group, reorder, reorder_many,
move_many and redefine now publish typed input and output schemas, so agents
and scripts can read exactly which parameters they take and what they return,
and the warn and strict parameter checks cover them.
Each parameter is read the way its handler reads it today, with the same error
messages, and the replies are built from the typed values and serialize to the
same JSON as before. Their lines leave unconverted.txt. Part of #2829.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* claim: #12629
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control channel: each step of engine.script runs as engine.execute runs it (#12629)
A script whose steps were all document commands ran them on the engine session alone, so the
window's part of each command was skipped: after `view.grid {"on": false}` the grid stayed on, and
the tool, the spin, the layout, preferences such as View Angles, and the first-body fit were missed
too. Now every step goes through `control::execute`, as each step of `engine.batch` does. The first
failing step still stops the script and the steps before it stay, with the same error words; a
script with a panel command answers as before.
The script's shape and its 100 000-step cap are read by one function, `script_steps`, for the
engine's own scripts and the window's.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A chamfer or fillet without `body` on a moved part failed with
"[20, 0, 0] is where 2 edges meet". This happened when another part
had an edge at that point in its own frame. Take 2 parts that meet
in the world, but not in their own frames. A pick on the shared edge
cut the first part. edge_points looked for edges on every body in
that body's own frame. It ignored the world point of the pick.
- frames.rs remembers the world point of every pick, face or not.
picked_world(local, nth) returns it. nth keeps 2 picks at one local
point apart.
- edge_points maps the world point into each placed body's frame,
hidden placements too. The world rows are built on first use. A
body's box, grown by the reach, culls it. The polylines short-list
edges. The exact curves decide (edge_foot: 33 samples, then a
golden-section search). Bodies within 2 d + 1e-3 mm of the nearest
refuse the pick. The reply names both: "[20, 0, 0] lies on edges of
2 bodies, Base block and Cap block: name the one to chamfer with
`body`". A hidden body adds "(X is hidden)".
- A seam at the pick, with no edge there, adds no second body.
- 2 placements of one body at one edge, or one local point, are 1 edge.
- With no placement to look through, the pick uses the bodies' own
frames. That happens when every frame is singular. The pick is not
passed unchecked.
- A pick's own `body` must exist and agree with `body`. Else the
command refuses: "an edge names `Cap block`, but `body` is `Base
block`: give one". Main finds the pick on Cap and cuts Base.
- The corner check runs on the body found. Corner and no-edge
refusals print the world point.
- With 2 or more bodies, the feature keeps the body found as `body`.
Picks on 2 or more bodies get 1 more check. If another
body's frame has an edge at a pick's point, the command refuses. The
reply asks for "each body in a command of its own". The ghost check
skips edges whose polyline is too far before it runs edge_foot.
- solid.press_pull forwards `body` to the fillet. Its frames entry
maps `body`, and its params name it.
- The fillet schema and the fillet and chamfer params describe the
lookup.
Changes a default: no. Calls that cut the wrong part now refuse,
naming both parts. So do picks on the cut of a split body. A pick
whose own `body` disagrees with `body` refuses too. Dialogs already
send `body`.
Synthetic scripts ran against 6a1fe7468 (solidcraft-cli run,
--profile ci). 4 picks now cut the picked part, where main refused:
- Base's edge, with Cap moved 50 mm up.
- The same, with the root active and Cap moved 500 mm along x.
- Cap's edge at its world point.
- Base's edge with tangent_chain false.
Parts meeting in one frame or in the world are refused, naming
both. Press Pull there is refused too; main rounds Base block. A box
edge on a cylinder's seam is cut; main tries the seam and fails.
Tests: 19 in frames_tests.rs (16) and tests.rs (3). 16 fail on
6a1fe7468. 3 pass there: hostile picks, a singular placement, and 2
placements of one part. action_matrix
edge_round_actions_keep_the_picked_body expects the new refusal.
cargo xtask ci: 13948 passed, 32 failed, 67 ignored, 89 binaries.
- 22 failures are in tests/KNOWN_FAILING_TESTS.
- 7 fail alone on 6a1fe7468 too:
3 pleat tests (FilletRearOuter, SMLib status 1001),
smlib a_cylinder_side_section_is_its_circle,
gpu gpu_thin_lens_focus_and_defocus_match_geometric_oracle,
ui-egui the_extrude_arrow_passes_faces_beside_the_profile_and_in_a_free_drag,
kernel boolean_props translation_invariant (a random 2-sphere case).
- 3 ui-egui agent_chat tests pass alone on both trees.
* claim: #10708
* Section Analysis: the ground shadow is no longer cut by the section plane (#10708)
The ground shadow was drawn with the see-through pipeline, whose fragment
shader discards everything past the section plane, so a section ended the
soft shadow in a hard straight line. It now uses the pipeline reference
planes already use, the same blend and depth test without the cut.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Agents and scripts: the occurrence commands, Capture and Revert Position, Move Bodies and Sketches to Component and Paste New describe their exact parameters and reply (#13186)
component.capture_position, component.revert_position, component.move_bodies,
component.move_sketches, component.paste_new, occurrence.copy, occurrence.drag,
occurrence.ground, occurrence.move and occurrence.properties now publish typed
input and output schemas. Each parameter is read the way its handler reads it
today and refused with the handler's own message, keys a handler reads only in
some cases (for example component with occurrence, tolerance with matrix) are
checked only in those cases, and the replies are built from the same types the
output schemas describe, so the JSON they return does not change. Their lines
leave unconverted.txt.
The base_transform check of occurrence.move is split out of the handler, and
move_vector and the matrix readers are made visible to the schema module, so
the contract uses the handler's own readers without changing what they accept.
Part of #2829.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* Schema: base_transform is 4 columns of 4 numbers, as the handler reads it
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
joint.origin, joint.rigid_group, joint.reach, joint.motion_link, joint.gear,
joint.rack_pinion, joint.screw and joint.cam now publish typed input and
output schemas. Each parameter is read the way its handler reads it today and
refused with the handler's own message. Keys that a handler reads only in some
cases (for example a joint origin's end, or a screw's a and b) are read only in
those cases. The replies are built from the same types the output schemas
describe, so the JSON they return does not change. Their lines leave
unconverted.txt. Part of #2829.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
sketch.create, sketch.construction, sketch.delete, sketch.move_point,
sketch.drag, sketch.drag_end, sketch.dimension_text, sketch.options,
sketch.chain and sketch.solve now publish typed input and output schemas.
Each parameter is read the way its handler reads it today, in the cases it
reads it, and refused with the handler's own message. Each reply is built
from the typed value and serializes to the same JSON as before. Their lines
leave unconverted.txt.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* Arabic: tanween in the canvas lock message and path tracing help, sketch term in the double Esc preference and Concentric help
* Arabic: timeline term in the Features under bodies note
* Arabic: created-holes wording and collapsed-folder term in the Group holes preference
* Agents and scripts: Insert DXF, Insert SVG and Export DXF describe their exact parameters and reply (#12508)
sketch.insert_dxf, sketch.insert_svg and sketch.export_dxf now publish typed
input and output schemas. Each parameter is read the way its handler reads
it today and refused with the handler's own message, and the replies are
built from the same types the output schemas describe, so the JSON they
return does not change. Keys the handler reads only in some cases (a path
next to text, a height next to a width, a scale next to either) are read by
the check in the same cases only. Their lines leave unconverted.txt.
Part of #2829.
* Insert DXF and SVG: the schema refuses a blank or too-long path when it is read
* Insert DXF and SVG: the schema checks height, scale and path only when they are read
The published schema constrained `height` next to a `width`, `scale` next to
either, and `path` next to a `text`, which the handler never reads. Their shapes
now apply only in the cases the handler reads them, matching `leave_unread`.
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
A SolidWorks 2011 vendor part (a SHELL_BASED_SURFACE_MODEL of three OPEN_SHELLs, two of
them closed, the third using faces against their surfaces) came in as nothing on the SMLib
kernel: "STEP import: no bodies (... not supported yet: SMLib renumbered the solid's faces)".
SMLib's export numbers such a body's edges and vertices otherwise than the reader, so the
face map was refused even though the faces were in the reader's order. Where the full
numbering does not match, the faces are now taken in the record's order when each one is
checked to be the record's face (same surface kind, loops, and corners).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Agents and scripts: the sketch projection commands describe their exact parameters and reply (#12505)
sketch.project, intersect, include_3d, project_to_surface,
intersection_curve, spun_profile, isoparametric_curve,
fit_curves_to_section, break_link and auto_project now publish typed input
and output schemas. Each parameter is read the way the handler reads it
today and refused with the handler's own message, and the replies are built
from the same types the output schemas describe, so the JSON they return
does not change. Their lines leave unconverted.txt.
The shape check of a reference is split out of the handler's reference
reader so the contract can use it without changing what it accepts.
Part of #2829.
* Sort the schema module list for rustfmt
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
* Hole: an option to put the holes one Hole command makes into a folder (#12492)
Preferences > Tools > Hole > Group holes made together (hole_group, off by
default). With it on, when one Hole OK makes two or more holes, they go
into a feature folder named "Holes" in History, closed at first; opened,
each hole is edited on its own as before. A lone hole and an edit make no
folder, and holes added inside a folder get their folder there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hole: a new Holes folder starts folded through the History's fold state
Since the History keeps each design's folds (#8886), fold the new folder
with timeline_folders::set_open instead of the browser's collapsed set.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hole: add several placements on one face
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Export test: count the box's grid mesh, not 12 triangles
each_format_has_an_export_command_of_its_own asserted a 20 x 10 x 5 box
exports as 12 triangles (an 684-byte STL). The mesher lays a planar face
out as a regular grid with sides at most a fifth of the body's diagonal
(kernel test a_box_is_a_regular_grid_on_every_face), so the box is 124
triangles and the STL 6284 bytes, on Linux and Windows alike. The test now
takes the count from the body's export mesh and checks the STL header,
the file size and the OBJ's faces against it; its line leaves
tests/KNOWN_FAILING_TESTS.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Export test: expect the box's grid count, worked out from the mesher's rule
Review: a count taken from export_mesh would follow an export that drops
triangles. The test now expects 124 from the grid rule (cells at most a
fifth of the diagonal: 5 x 3 x 2 along 20 x 10 x 5).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
document.inspect, engine.commands, inspect.measure, material.assign,
material.list and sketch.inspect now publish typed input and output schemas.
Each parameter is read the way its handler reads it today, and is refused with
the handler's own message; inspect.measure reads only `items` when it is given
and `bodies` and `names_only` otherwise. The replies are built from the same
types the output schemas describe, so the JSON they return does not change.
Their lines leave unconverted.txt.
Part of #2829.
* Agents and scripts: Auto Constrain, the constrainer, snapping and the curvature and polygon constraints describe their exact parameters and reply (#12506)
sketch.auto_constrain, sketch.auto_constrain_from_datum,
sketch.finish_auto_constrain, sketch.constrainer, sketch.constraint.curvature,
sketch.constraint.polygon, sketch.toggle_driven, sketch.glyphs and sketch.snap
now publish typed input and output schemas, so agents and scripts can read
exactly which parameters they take and what they return, and the warn and
strict parameter checks cover them.
Each parameter is read the way its handler reads it today, with the same error
messages. The replies are built from typed values and serialize to the same
JSON as before. Their lines leave unconverted.txt. Part of #2829.
* Drop constraint_result, which no command uses any more
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
* Check the coincident snap's point id in the reply test
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
sketch.line3d, sketch.spline3d, sketch.point3d and sketch.move3d now
publish typed input and output schemas. Each parameter is read the way
the handler reads it today and refused with the handler's own message,
and the replies are built from the same types the output schemas
describe, so the JSON they return does not change. Their lines leave
unconverted.txt.
Part of #2829.
parameters.change, list, users, graph, export, import, complete and
expr.evaluate now publish typed input and output schemas. Each parameter is
read as its handler reads it today, with the same error messages; keys a
handler only reads in some forms (changes, delete, favorites, text, a text
unit) are checked only in those forms. Each reply is built from the typed value
and serializes to the same JSON as before. Their lines leave unconverted.txt.
sketch.arc.tangent, sketch.blend_curve, sketch.centerline,
sketch.circle.two_tangent, sketch.circle.three_tangent, sketch.conic,
sketch.edit_text, sketch.ellipse, sketch.infinite, sketch.line.midpoint,
the three slot commands that were missing, the three spline commands
and sketch.text now publish typed input and output schemas.
Each parameter is read the way its handler reads it today and refused
with the handler's own message. Keys a handler reads only in some cases
(diameter without a radius, end without a sweep, minor without a minor
radius, the keys of one text outside edits) are checked only in those
cases. The replies are built from the same types the output schemas
describe, so the JSON they return does not change. Their lines leave
unconverted.txt.
Part of #2829.
* docs: RFC 0001, the Craft bridge between SolidCraft, CamCraft and PCBCraft
Starts docs/rfcs/ for cross-app proposals. RFC 0001 is a draft: one
.craftx exchange file with stable face names, an open-in launch, file
watching for live links, and a phased plan starting with Send to CamCraft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: RFC 0002, plugins
Turns the design in #1221 and docs/plugins.md into an RFC. docs/plugins.md
becomes a stub pointing to it; the two links to it are updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: keep the RFC change docs-only
docs/plugins.md stays as a pointer to the RFC, so the code comment needs no edit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12092
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Tangent: a line's end point and a curve make a tangent joint (#12092)
Tangent took two curves only and refused a picked end point with
"unknown sketch curve l1.end". It now takes a line's or an arc's end
point with a curve: the end lies on the curve (point on curve) and the
two curves are tangent there, in one undo step. Two end points make the
ends coincident and the curves tangent. Only the picked end moves, so
the line turns about its other end and gets longer or shorter; the
other curve stays.
Parallel, Perpendicular, Equal and Collinear take no point, so a click
on a line's end point now picks the line for them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Tangent: at a joint, add only the tangent (#12092)
When the picked end already lies on the other curve (it is that
curve's end, or a coincident joins them), Tangent added a redundant
point-on-curve, and a coincident given in the other order came back as
the existing constraint but was reported as new. Now such a constraint
counts as held and is not added. The end can't leave the other curve
at a joint, so the curve it ends stays and the other curve moves.
Raised in review by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Tangent: x and y name the axes before a point does (#12092)
Tangent looks for an end point only when an operand names no curve and
no sketch axis. It checked for an existing axis line, so before that
line was created a point called x or y took the axis's place. Now x and
y always name the axes, as for the other constraints that take a line;
a curve with that id still wins.
Raised in review by CodeRabbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* History: an option for a folder of one kind of feature to show its icon (#12514)
Preferences > Layout > Folder icon from its features
(history_folder_kind_icon, off by default). With it on, a History folder
whose features (including those in folders inside it) all have the same
icon, and that holds no joints, shows that icon instead of the folder
icon, keeping its count; in both the History bar and the side History.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Preferences: Folder icon from its features in a row of its own
One more box in the row of History and browser options widened the
Layout page past its edge at narrow windows, pushing the navigation
bar's segments outside it
(navigation_bar_appearance_segments_stay_inside_the_page_at_narrow_widths).
Also give the new messages in Korean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
view.save, view.apply, view.rename, view.delete, view.list and
sketch.redefine now publish typed input and output schemas. Each
parameter is read the way the handler reads it today and refused with
the handler's own message, and the replies are built from the same
types the output schemas describe, so the JSON they return does not
change. view.apply only looks at the camera when no view name is given,
as the handler does. Their lines leave unconverted.txt.
The camera checks of view.save and view.apply, and the note that
sketch.redefine adds for a world plane in a moved component, are split
out of the handlers so the contracts use the same code.
Part of #2829.
frame.member, frame.profile.insert, frame.profile.list and
frame.profile.properties now publish typed input and output schemas. Each
parameter is read the way its handler reads it today and refused with the
handler's own message, and frame.member reads the profile-sketch keys only
when the handler does. The replies are built from the same types the output
schemas describe, so the JSON they return does not change. Their lines leave
unconverted.txt.
Part of #2829.
timeline.copy, timeline.paste, timeline.add and timeline.export_script now
publish typed input and output schemas, so agents and scripts can read
exactly which parameters they take and what they return, and the warn and
strict parameter checks cover them.
Each parameter is read the way the handler reads it today, with the same
error messages, and the replies are built from typed values that serialize
to the same JSON as before. Their lines leave unconverted.txt. Part of #2829.
component.activate, create, delete, from_bodies, list, properties, rename
and unlock now publish typed input and output schemas. Each parameter is
read the way its handler reads it today and refused with the handler's own
message, and each reply is built from the typed value and serializes to the
same JSON as before. Their lines leave unconverted.txt. Part of #2829.
* Agents and scripts: Create, Delete, List, Inspect, Limits, Drive, Solve and As-Built Joint describe their exact parameters and reply (#12503)
joint.as_built, joint.create, joint.delete, joint.drive, joint.inspect,
joint.limits, joint.list and joint.solve now publish typed input and output
schemas. Each parameter is read as its handler reads it today, with the same
error messages, in the same modes, and each reply is built from the typed
value and serializes to the same JSON as before. Their lines leave
unconverted.txt. Part of #2829.
* Joints: a joint origin's points take {x, y, z} in the schema too, as the handler reads them
explode.create, explode.delete, explode.list and explode.show now publish
typed input and output schemas. Each parameter is read as its handler reads
it today, with the same error messages (scale is only read when moves is
not given, and show treats a missing or null view as "none"), and each reply
is built from the typed value and serializes to the same JSON as before.
Their lines leave unconverted.txt.
* claim: #12953
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Typed schemas for Line, Point, Circle and Arc (#12953)
sketch.line, sketch.point, sketch.circle.center, sketch.circle.two_point,
sketch.circle.three_point, sketch.arc.three_point and sketch.arc.center_point
get typed input contracts and output schemas in schema/sketch_shapes.rs, and
leave unconverted.txt. The replies keep the same JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Yz6dHZiXcgTgrxjVNC6hx
* Blend test: give the chord line its id with `ids`
sketch.line takes `ids`; the `id` it was given was ignored, which the
parameter audit flags as a call strict would refuse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Yz6dHZiXcgTgrxjVNC6hx
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Czech: translate the 748 missing UI strings and fix 3 untranslated ones
The Czech catalog now covers every English message. New translations
go in assets/locales/pending/czech-complete.json; three messages that
were left in English in cs.json (Zoom, Polygon, Start) are translated
in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Czech: name the render methods like their labels (Trasování paprsků CPU, Trasování cest GPU) and fix the direction of the graphics page subtitle
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11945
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: body features data, model.body_features and the Browser's Features under bodies (#11945)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: fmt; engine body features test passes (#11945)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* kernel-cases: model.body_features is a listing with no kernel case
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: a feature listed under two bodies is a Shift-click range end at each of its rows (#11945)
Each feature row under a body takes its own row-order key, <body row>/s:<id>, which still selects the feature, so a Shift-click on its second row no longer starts the range at its first row under another body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Release 0.40.0-rc.3
Bumps the version to 0.40.0-rc.3 and folds the 15 pending format changes into format 51.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Doc test: check the saved texture resource by its key, not the pending format name
Folding embedded-surface-textures into format 51 stops the design naming it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Start release notes at the newest published release
The nightly release job has failed since Nov 4 with HTTP 422 "body is too long": generated notes
started at the Latest stable release, v0.30.1, and grew past 125,000 characters. Start them at the
newest published release instead, pre-releases included. Let a refused Snap Store upload stop
failing the job, after the release is already staged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Pick the start tag by publication time; leave the Snap Store step as it was
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Expressions: floor, ceil and trunc ignore float noise from unit conversion
* Expressions: snap only a few ulps to a whole number in floor and ceil
* Expressions: never snap a tiny value to zero in floor and ceil
A History folder that holds only holes (with the folders inside it) gets
Edit Holes in its right-click menu, in the History bar and the side
History, and over the control channel as ui.editFolder. It opens one
Hole dialog filled in from the first hole, with nothing to pick; OK
redefines each hole with the fields changed there, and each hole keeps
its own place and everything not changed.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11833
* wip: Measure: exact plane-to-plane distance with real ends (#11833)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Measure: datum planes' meeting point from the cross product, exact near parallel
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Measure: the 1 and 2 end numbers never cover each other or the distance (#11833)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11125
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: sketch.replace command, tests and Browser menu entry (#11125)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: menu test, formatting (#11125)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* kernel-cases: sketch.replace is 2D sketch only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Replace Sketch: new ids never reuse the old contents' ids; a tool's picks on the replaced sketch are dropped
A reference to an old curve (a profile picked by its curves, a sweep path) now finds nothing and
the feature reports it, instead of quietly binding to a new curve that got the same id. Replacing
the sketch being edited clears the active tool's picks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12085
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a sketch that lost a projected edge by name opens for editing with its curves and the lost link marked (#12085)
The sketch still fails and features built from it still fail; while it is
edited, the model holds it built with the lost named link marked lost, in the
final state only, so it can be repaired from inside.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a failed sketch held for its editor is built again on every evaluation (#12085)
When the sketch and every feature after it fail, the results end in the
same state as before an edit, so the held sketch kept its old geometry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12720
* Browser: Edit Feature from a component's Features folder gives the active component back (#12720)
A double-click on a feature row inside a placed component activates that
component and opens Edit Feature, so the edit happens in its placement.
OK and Cancel only closed the editor: the component stayed active, every
other body stayed faded and the History bar stayed filtered on it.
The double-click now remembers the component and placement active before
it, and the end of that feature's editor, however it ends, makes that one
active again, as leaving a sketch opened the same way does (#8127). A
component activated while the editor is open stays active.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Mek <mf@linescode.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
The test helper mapped() in job/host_tests.rs reads a texture's encoded
bytes through TextureMap::source() and TextureSource::encoded_weak(), which
exist only with raytrace-cpu, but it wasn't gated, so the render crate's
tests failed to compile when it was checked on its own. The helper and the
two registered-executor tests that call it now take the cfg the helper's
other callers and its sibling cpu() already have. Workspace builds, which
turn the feature on, run all of them as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(pl): translate the render workspace (1412 messages)
Every render.* message of en.json: output, lighting, environment, stage,
studios, appearance editor, lens effects, aging, shots and the render
browser. Added as one pending locale file.
* i18n(pl): translate 1290 missing UI messages
Everything else en.json gained since the last catch-up: Preferences,
tool help texts, panels, construction lines, fillet options, import
report, personal sheet metal rules, joint limits and recovery. With the
render file, pl covers every message of en.json.
* i18n(pl): "Of" picks a joint's motion, not an object
The nested Of row in the motion study and motion trace dialogs chooses
which of a joint's motions (turn or slide) the track drives.
`cmd.construct.plane.axis_point` failed with "the point is on the axis"
when the user selected a circular edge whose center lies on the construction
axis (e.g. rim of a cylinder, revolve, or torus). This occurred because
`point()` resolved `GeoRef::Edge` to its center, which is collinear with
the axis of revolution.
Resolve the edge pick to the nearest point on the edge polyline, projecting
onto the exact circle geometry via `kernel::edge_circle` when available,
enabling unique planes through the axis at any picked azimuth around the rim.
Changes a default: no
Fixes#12851
* Update the solidcore kernel to main 99ed57c4d4 for rc.3
The pin and its API changes, nine retired flags dropped, chamfers through
prepare_chamfers_along at the sizes asked, the whole-body section join, the
test and list updates this pin needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Format crates/smlib/src/core/blend.rs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: bherbruck <bherbruck1@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* STEP import: tests for an edge or curve on a CURVE_REPLICA (#10755)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* STEP import: read a CURVE_REPLICA as its parent moved by the transformation (#10755)
An edge whose curve is a CURVE_REPLICA (FreeCAD writes one for a repeated
edge) was refused as an unsupported curve, leaving the body open. The reader
now keeps the parent and the CARTESIAN_TRANSFORMATION_OPERATOR_3D; edges use
the parent moved and scaled as the same kind of curve; the writer round-trips
it. A mirroring operator and a replica as a swept profile stay unsupported.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* STEP import: read CARTESIAN_TRANSFORMATION_OPERATOR_3D with its seven parameters (#10755)
The entity has a name and a description before its axes, origin and scale.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Sketch Intersect: test that a round refitted to the exact section still meets its neighbours
A failing test first. The section fit re-fits each arc to the exact crossings and moves only the
arc's own ends onto the new circle, so the lines beside it no longer meet it and the sketch finds
no closed profile (tangency-labs/solidcore#2908, point 1). `refine_rounds` wraps the per-curve
refit unchanged, so the test can call it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: a round refitted to the exact section keeps meeting the lines beside it
The section by a plane fits lines and arcs to the mesh crossings, then re-fits each arc to the
crossings that lie exactly on the surface. That refit moved only the arc's own ends onto its new
circle, so a wall beside an R2 round ended up to 1.2e-3 mm away from it, and the sketch, which joins
ends only within 1e-7, found no closed profile. A line meeting an arc's end now moves with it; an
arc that meets another curve there keeps its fit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: the refit test's crossings step by pi/16 with a constant that exists
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: the refit test names the distance a profile joins ends within (1e-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: test that a round beside another arc is refitted when their joint stays put
A failing test first, for CodeRabbit's point on #12722: the fix kept the first fit of every arc
that meets another arc, even where the refit leaves their joint where it was. Before the fix such
a round was refitted, and that more exact result should stay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: a round beside another arc is refitted unless that tears their joint
The refit now runs for every arc. Lines that met its ends move with them, as before. Where an end
meets another arc, the refit stands when that end moves no further than the fit's join distance
(1e-7 of the size). Otherwise the arc keeps its first fit. So a round that was refitted before
tangency-labs/solidcore#2908 still is, unless the refit would open the outline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: test that a round beside another arc is refitted while its joint moves less than a profile joins across
A failing test first, for CodeRabbit's follow-up on #12722: with the joint moving 1e-5 to 9e-5 mm
the refit closed the profile before the fix, but the fix kept the first fit, because it guarded
the joint with the fit's join distance (1.4e-6 mm here) instead of the profile's 1e-4 mm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: a round beside another arc keeps its refit while their joint stays within a profile's join distance
The guard on a joint between two arcs now asks whether the refitted end stays within
ENDPOINT_JOIN_TOL (1e-4 mm, the distance a profile joins ends across, now public in
solidcraft-sketch) of where the other arc ends now, which may itself be refitted. Before, it asked
whether the end moved more than the fit's join distance (1e-7 of the size), which kept back refits
the profile would have closed across.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: test that two arcs refitted the same way keep their joint
A failing test first, for CodeRabbit's next point on #12722: when a round and the arc beside it
both miss their circles by the same shift, both refits move their joint to the same point. Before
the fix both were refitted. bd7db2626 compares each refit with the other arc as it was fitted, so
past 1e-4 it keeps both first fits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: two arcs refitted the same way keep their refits and their joint
refine_rounds now refits every arc first, then takes refits back where two arcs met as fitted and
their ends, as they would now stand, are further apart than a profile joins ends across. It
repeats until no joint tears. So two arcs whose refits move their joint the same way both keep
them, and an arc is only kept as fitted where a refit would open the outline.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: ENDPOINT_JOIN_TOL's comment says it bounds the search for joined ends, not that ends within it always join
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: the two-arc refit test checks that the whole outline stays closed
At e4c2545f8 the lines after an arc lost their move to the refitted arc's end, which the test
missed because it only looked at the joint between the arcs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Intersect: lines after a refitted arc keep their move to its ends
refine_rounds re-set every non-arc piece from its fit after the arcs were done, which undid the
move of each line that comes after the arc it meets. Only circles are refitted there now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12086
* wip: a projected face tells a thin arm's end corners apart by the sense of their lines (#12086)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: lines leaving a projected face's corner match one to one (#12086 review)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12324
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a curve ending on a spline between its samples closes the regions it bounds (#12324)
Free-form curves take part in profile detection as polylines with a 1e-3
chord tolerance, while curve ends are joined within 1e-5. A spline (or any
curve) ending on a spline between two samples, on the side away from the
chord, ended short of the polyline and was pruned as dangling, so the
regions it closes were not selectable. The polyline now passes through every
curve end lying on the exact segment.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A tile on the Home page centres its title and a caption of up to two
rows. When the two are taller than the tile (a system font with tall
lines, such as the Chinese font on macOS), centring put the title's top
above the tile, where it was clipped. They now start at the top of the
text area instead, so the caption's end is clipped rather than the
title.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #9368
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Extrude: a profile still found after a sketch line cuts off a sliver or splits an offset curve (#9368)
A picked profile of lines, arcs and circles no longer fails when a line ending on
a circle gives it more arcs, and an analytic offset's loop that a line now cuts
falls back to its pieces (step 1 only). Free curves and free-form offsets keep
their exact meaning.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: joint springs, motors and motion links (refs #9719)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dynamics: joint springs, motors and motion links (refs #9719)
Joint spring-dampers and motors act along joint values (revolute, slider, cylindrical and planar) as generalized forces; drives make a joint value follow a time profile (constant, linear, cycloidal ramp, harmonic, table) as a rheonomic constraint and report the effort it takes; motion links tie two values linearly (gear, rack and pinion, screw). Values they use are tracked in the state so angles count whole turns. The energy now carries the work put in (motors, drives, loads) and the energy dissipated, so total + dissipated - input stays constant. Integrators pass time to the stages (Dormand-Prince nodes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12372
* Measure: area of a flat face from its exact edges, less its holes
A flat face's area was taken from the display mesh. The mesh's polygon for a
round hole runs inside the circle, so the holes came out too small and the face
too big (2293.875 mm² instead of 2293.186 mm² for a 60 × 40 plate with holes of
radius 3 and 5).
The area of a flat face is now worked out along the exact edges of each loop
(Green's theorem): a line exactly, a circle or an arc in closed form, any other
curve with Gauss quadrature. The face's area is its outer loop's area less the
areas of its holes. When an edge does not lie in the face's plane, Measure falls
back to the mesh as before.
Fixes#12372
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Measure: a flat face's straight edges must lie in its plane; test an elliptic hole
A straight edge whose ends are off the face's plane now sends Measure back to
the mesh, as an off-plane curve already did.
A new test cuts an elliptic hole through a plate. Its edge is a spline, so it
goes through the Gauss quadrature path, on both faces. Without that path the
face read 2024.696 mm² from the mesh; it now reads πab within 10⁻² mm² (the
kernel's spline for the ellipse encloses 2·10⁻³ mm² more than πab).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Render tests: skip two CPU raytracing tests when raytrace-cpu is not compiled in
cpu_atmosphere_pointer_fields_preserve_saved_camera_and_roundtrip_with_one_undo_each
and live_image_imported_mesh_keeps_selected_and_hovered_face_and_body_feedback
need CPU raytracing, which solidcraft-ui-egui only gets through feature
unification with the apps in a workspace build. Run on their own
(cargo test -p solidcraft-ui-egui) they failed. They now skip the same
way as their neighbours (live_cpu_fixture and the other CPU tests).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render tests: the CPU Atmosphere pointer-field test is native only, like its neighbours
On wasm the platform CPU host is the cooperative one, which never accepts
CPU raytracing, so the test could only ever skip there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Preferences > Tools > Hole > Several holes on one face (hole_several, off
by default). With it on, in Single placement a pick elsewhere on a face
that already has a hole adds another hole, and a pick on a hole takes that
one out; each hole keeps its own drag handle, and OK makes one Hole
feature per hole. A round face split at its seam still gives one hole per
pick. Off, a face takes one Single hole as before.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12783
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render settings: input and output schemas for Render Settings and Save Render Settings (#12783)
Typed contracts and reply types for render.settings.get and
render.settings.set in a new schema/render_settings.rs, wired with
checked!, .contract and .output; the two leave schema/unconverted.txt.
The handlers build their replies from the new types through typed_reply;
the JSON is the same.
SETUP_FIELDS lists RenderSetup's 15 fields once. Save Render Settings'
schema and HDR capture's schema both publish it, and patched_settings
takes its bound from its length, so they can't drift: HDR capture's
schema had kept 13 as its bound and listed 14 fields without `coats`,
refusing patches the handler takes. A test checks the list against the
fields serde names for RenderSetup.
`name` reads with the setup name reader Apply Render Camera's contract
uses (now visible to the module beside it). Save Render Settings' `name`
and `set` are optional in the type and required in the schema, so a call
without them keeps serde's "missing field"; `null` and wrong types are
refused with the handler's message, and their published `null` default
is dropped (drop_default, also made visible).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Save Render Settings: the check refuses a call without name or set
`name` and `set` were optional in SetParams, so the check passed a call
without them and the handler refused it with serde's "missing field".
They are required fields now: the check refuses the call itself, with
"`name` is required" or "`set` is required", and the published schema
lists both as required (no `default: null`, so drop_default is private
again in render_camera.rs).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
A modelled thread used to cut the whole body as facets: the Thread feature
swept the groove as a mesh and cut it with faceted_boolean at a tolerance of
a two-hundredth of the pitch, and faceted_boolean faceted the body operand
too. Every other cylinder in the body came out of that cut as a stack of
small planar faces, so the next hole's wall was no longer a cylinder and
thread_on refused it with "no cylindrical face there to thread".
The cut is exact now (the groove is an SMLib screw sweep cut by the exact
B-rep boolean), and faceted_boolean no longer facets the body. Both came
with the SMLib kernel merge, 34 minutes after the issue was filed, and the
reporter and the maintainer both reproduced on 0.30.1, which predates it.
This pins the behaviour down with tests, at the two layers the bug was
reported through: the model layer (the other hole's wall is still a cylinder
to thread) and the CLI (a modelled M6 thread in one hole of a block with two
holes, then a thread in the second, at gaps of 40, 20, 12 and 8 mm).
Fixes#11367
Co-authored-by: vdirienzo <vdirienzo@users.noreply.github.com>
Six ui-egui tests dropped an egui frame output without clearing its
texture changes. epaint debug-asserts that a dropped TexturesDelta is
empty, so a plain `cargo test -p solidcraft-ui-egui` (debug assertions
on) failed them; CI's `ci` profile turns debug assertions off and never
saw it. Each test now clears the texture changes right after the frame,
as the rest of the crate's tests do.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A body of two pieces joined apart could not be split by a plane through
both of them: the split stopped with "the tool does not split the body".
The split keeps only the non-empty halves the kernel's half-space cut
gives it, and refuses when fewer than two come back. For a body in two
pieces the half-space cut of one piece came back empty, so fewer than two
halves came back and the split was refused.
The behaviour is already right in the tree: the SMLib kernel merge
ea772a043 (#11351) brought it. This commit pins it down with tests, at the
two layers the bug was reported through, so it cannot come back:
* crates/doc/src/tests.rs - a_split_of_a_body_of_two_pieces_gives_the_halves_of_both
* crates/engine/src/tests.rs - split_body_of_a_body_of_two_pieces_splits_both
The engine test is the reporter's and the maintainer's scenario: one
sketch with two squares 20 mm across and 30 mm apart, both extruded 10 mm
as New Body, then Combine (join), then a plane through the middle of both
squares.
Fixes#6738
Changes a default: no
* Parameters: a refused new parameter's message goes once the draft is corrected
In Parameters, adding a parameter whose name is taken, or whose expression does not evaluate, showed the reason in the window's banner. The banner stayed after the name or the expression was corrected, until the next Enter, beside an expression cell that already said the draft was fine.
The add row now keeps the draft it was refused with (name, unit, expression, comment), the design document the refusal was about and the message, as a command window does for a refused OK (#641). Each frame the window drops the message once the draft differs or the document is another one; a selection or display change keeps the document and so keeps the message. The refusal lives apart from the row edits' messages: the banner shows the last action's message if there is one, else the refusal. Discarding the row takes the message with it. The add row's bin button publishes the scenario handle param:new:delete.
Eight asserts read the banner through ParamsDialog::banner() instead of the error field. Regression tests: correcting_a_refused_new_parameter_clears_its_error and seven more in params_dialog.rs cover the corrected draft, a discarded row, a refusal on leaving the row, a design change, a row's own message beside the refusal, a selection, a unit or comment edit, and the banner's order.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Design Properties: a refused OK's message goes once the fields change
In File, Design Properties, a value the design refuses (a part number with a control character, for example) keeps the window open with the reason. The message stayed after the field was corrected, until the next OK.
The window now keeps a snapshot of the fields at the refused OK, as a command dialog does for its inputs (#641). Each frame it compares the fields with the snapshot; once they differ, the message goes, also on an edit to another field, and the next OK checks again. The regression test the_window_edits_properties_in_one_undo_step_and_keeps_other_tools_data covers the corrected field, an edit to another field and the repeated refusal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* BOM panel: a refused Apply's message goes once the row or its fields change
In the Bill of Materials window, edit a part number to one the design refuses and click Apply: the message shows. Correct the field, or pick another row: the message stayed until the next Apply. BomPanel::apply set the error on a refusal and nothing cleared it while the window was open.
The panel now keeps the design revision, the row and the fields Apply ran with and drops the message as soon as any of them differs, as a command dialog's refused OK does (#641); the next Apply checks again. The revision is part of the snapshot because the row index counts the design's rows, which an undo or a part added elsewhere can change under the message. The regression test a_refused_apply_keeps_its_message_until_the_row_or_its_fields_change covers the corrected field, another field, another row, an undo and the repeated refusal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Material editor: a refused Save's message goes once the draft changes
New material, name it Steel (a built-in), Save: refused with the reason. Rename it: the message stayed until the next Save. The editor set the error when save was refused and cleared it only on a successful save, Discard or a new draft.
The editor now keeps the design revision and the command Save ran with and drops the message as soon as the draft would run another, or the design changes under it, as a command dialog's refused OK does (#641); the next Save checks again. A Save refused while the draft fails its own validation (not reachable with a mouse, Save is disabled then) is kept the same way. The regression tests physical_material_refused_save_keeps_its_message_until_the_draft_changes and physical_material_refused_save_goes_when_the_design_changes in manage_dialogs_tests.rs cover the rename, an undo and the repeated refusal.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Add agent: a refused Add's message goes once the fields change
Add agent, custom, click Add with the command empty: the dialog asks for a command. Type the command: the message stayed until Add was clicked again, the same for a missing model, a key without https or a bad URL. The dialog cleared the message only on a preset, kind or model change.
The dialog now keeps what the fields said when Add was refused and drops the message as soon as any of them changes, as a command dialog's refused OK does (#641); the next Add checks again. The snapshot holds whether a key is typed, not the key itself. The regression test a_refused_add_keeps_its_message_until_the_fields_change covers a program without a command and an endpoint without a model.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Web app: test that a staged restore counts only once its file is in
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web app: restore and recover a large design in stages, with progress and Cancel
Restore and Open saved version in "Restore unsaved design?" and Recover in "Recover unsaved work?" opened their design with open_path, which reads and evaluates it on the page's only thread before returning. They now open through open_path_answered, a few features a frame, and what follows the open (the design gets back its file and name and is marked unsaved, the kept copy or the recovery goes) waits for the answer that it is in. Neither window comes back while the open runs.
SolveApp::open_path is now pub(crate), with a line saying it blocks; the one test outside the crate that used it opens with open_path_async and waits for the load.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: paste and move sketches while an exploded view is shown save what they save without it
Red first: the four commands work the saved geometry out from the exploded placements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Paste and Move Sketches: leave an exploded view shown out of the saved geometry
timeline.paste (within a design and from another design), component.paste_from_design and
component.move_sketches worked the pasted or moved geometry out from placed_document(), which
includes the placements an exploded view shows. Use design_document(), as component.transfer does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Section Analysis: a construction plane in a moved component cuts where it is drawn
Make or insert a component away from the origin (or move it), activate it,
add a construction plane, then Section Analysis on that plane. The cut ran
where the plane would be if the component sat at the origin, while the
viewport drew the plane where the component stands, and the reply's origin
said the unplaced position. `inspect.section` read the plane from the
component frame; it now reads it from the placed model, like every other
world reply.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
* Section Analysis: the placed-plane test turns the component too
A translation alone cannot tell a placed normal from an unplaced one.
The test now turns the component 90 degrees about X as well and takes
its expectation from the placed box as inspect.measure answers it: the
cut halves the box along Y and the normal lies along Y.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
`inspect.measure` lists a further placement's body as `KB (K:2)`, and
`appearance.assign` and `body.rename` take that name for the body the
placement shows. Three commands did not: `plastic.assign_rule`,
`body.repair` and a configuration column by body (`config.column` with
`material` or `appearance`) each answered "no body" for it. They now
resolve the name the same way (`own_body_name`), so an agent can act on
the name it was given. These commands take no picks, so the name is
mapped in the handler without placing the model.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
With a face picked, the Joint dialog's frames took 462 ms on a
1,956-face part. With both sides picked, they took 593 ms. snap_of
read a face's centre with Body::faces, which meshes the whole body.
A round face meshed it again in cylinder_face_at. The dialog's
commands and the picked glyphs called it every frame. The hover glyph
called it on each new hovered item.
- snap_of measures a face with ModelBody::joint_faces (now pub). Joint
origins already use that cache, at the same tolerance.
- SolveApp keeps each pick's snap (picked_snap). It holds while the
world state, design and open sketch keep the same Arcs. Any change
starts a fresh memo. The dialog's commands, the picked glyphs and
the hover glyph read it. It keeps the 16 latest, so hovering across
a part stays bounded.
- A world rebuild that reuses an unchanged placement shares its face
measurements too. Moving 1 part re-measures that part alone.
In the app, 10 s pointer sweeps give these median frames. With 1
pick: 462 ms before, 1.9 ms after. With 2 picks: 593 ms before,
1.9 ms after.
Test: a_picked_snap_is_measured_once_not_every_frame picks a hole wall
and a face. It then runs 3 frames plus the OK and preview commands.
Without the memo they tessellate for 26.7-27.2 ms in 4 runs. Without
the memo and the joint_faces read, 42.2-44.2 ms in 3 runs. With both,
0 ms. The memo matches a fresh measurement within 1e-9, and follows
a moved part.
cargo xtask ci: 13,644 passed, 36 failed, 67 ignored. 35 of the
failures also fail alone on upstream/main 871014f95. The other,
scale_invariant, is a random proptest case. It passes 3 of 3 runs
alone on both.
Creating a joint rebuilt every feature after an Extrude From a face.
So did moving a part. That held even when the face's part stayed put.
placement_digest set reads.all for an Extrude with a From or To face.
It did the same for a Revolve To. Their cache key then took every
part's placement. Moving a third part changed that key. The chain
fingerprint then rebuilt every feature after it (#6183).
- placement_digest reads the bodies the stored face names find
(PlacementReads::object_faces). Each comes with its own placement,
as a combine reads its target. A join or cut also reads its
targets, as other drawn tools do.
- It falls back to every placement when a name is empty or missing.
It does the same when the name finds no body, or more than one. It
also falls back when there is no input state. The face is then
found by its point, on whatever body lies there.
- name_lookup::face_body finds the one body holding a stored name's
face. It matches names exactly, as locate_face does.
No miss record is needed. with_named_face_points moves the point onto
the named face first. It fails the feature with "reselect it" when no
one face has the name. object_face's point search with a stored name
doesn't run.
Tests, in eval_cost_tests beside the cross-part cut test:
an_extrude_from_its_own_part_keeps_its_result_when_another_part_moves
and a_revolve_to_its_own_part_keeps_its_result_when_another_part_moves.
Moving the other part recomputes 1 feature on main, 0 here. Moving the
plate still recomputes 1, and the result follows it (#928).
an_extrude_from_a_face_without_a_name_extrudes_again_when_any_part_moves
holds the fallback.
The test part has 1,956 faces and 38 features after the extrude.
joint.create took 26.9 s before and takes 80-84 ms after.
occurrence.move of the third part took 26.7 s before, 0.1 ms after.
cargo xtask ci: fmt, clippy and the wasm check pass. 13,647 passed,
35 failed, 67 ignored. The 35 fail alone on upstream/main 871014f95 too.
* Claim #12874: Interference schemas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Interference: input and output schemas for inspect.interference
`InterferenceParams` gives the check its keys, `bodies` as a name list with
the handler's messages and `budget_s` bounded as the handler bounds it. The
reply is `InterferenceResult`, with the keys it had before. The command
leaves schema/unconverted.txt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* i18n(ca): catch-up amb en.json del 2026-11-07 (432 noves, 24 canviades)
Catch-up with `en.json` at `871014f95`.
* i18n(ca): el títol REST és Recolzament, com l'ordre i l'ajuda
---------
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
timeline.compute_all, delete, dependents, edit, inspect, rename, rollback,
roll_to and suppress now publish typed input and output schemas. Each
parameter is read as its handler reads it today, with the same error
messages, and a key a handler reads only in some cases (position when a
feature is given, feature when a group is given) is read only then. Each
reply is built from the typed value and serializes to the same JSON as
before. Their lines leave unconverted.txt.
Part of #2829.
sketch.constraint.coincident, collinear, concentric, equal, fix,
horizontal_vertical, midpoint, parallel, perpendicular, symmetry and tangent
now publish typed input and output schemas. Each key is read the way its
handler reads it today, in the same cases (for example `line` is not looked
at when Horizontal/Vertical gets `points`, and `a` and `b` are not looked at
when Equal gets `curves`), and is refused with the handler's own message. The
replies are built from the same types the output schemas describe, so the
JSON they return does not change. Their lines leave unconverted.txt.
Part of #2829.
* Claim #12917: Combine schemas
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Combine: input and output schemas for solid.combine
solid.combine gets an input contract (CombineParams) and an output schema
(CombineResult: the feature result plus the new component's id). Each key
reads with the handler's messages, so strict mode keeps today's errors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
sketch.break, sketch.chamfer.distance_angle, sketch.chamfer.equal_distance,
sketch.chamfer.two_distance, sketch.extend, sketch.fillet, sketch.mirror,
sketch.move, sketch.offset, sketch.offset.break, sketch.pattern.circular,
sketch.pattern.rectangular, sketch.scale, sketch.trim and sketch.trim.path
now publish typed input and output schemas. Each parameter is read the way
the handler reads it today, with the same error messages, and keys a handler
only reads in some forms are only checked in those forms. The replies are
built from typed values and serialize to the same JSON as before. Their
lines leave unconverted.txt.
Part of #2829.
The six render library commands (appearance, environment, physical,
surface and studio apply and prepare) now have typed input and output
schemas, so an agent can see exactly which parameters they take and what
they reply with. The commands behave as before; only what they report
about themselves is new.
* claim: #12622
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Plastic features: input and output schemas for Boss, Lip, Snap Fit and Rest (#12622)
Typed contracts for plastic.boss, plastic.lip, plastic.snap_fit and
plastic.rest in schema/plastic.rs, wired with checked!, .contract and
.output; the four leave schema/unconverted.txt. Each reply is the
feature commands' FeatureResult.
Each key reads as its handler reads it, with its message: points and
directions through a small plastic_vector! type (a direction must not be
zero), sizes and angles as LengthExpr, AngleExpr and NumberExpr, and the
common feature keys through FeatureCommon. `touching` is declared because
the features join through the shared Boolean step. Lip's `type` and
`side` are new keyword_param! types in schema/types.rs.
The contracts refuse optional keys of the wrong type that the handlers
read as left out today (a size that isn't a number or text, a `body`
that isn't text, an `outside` or `new_body` that isn't true or false).
Tests in schema/plastic_tests.rs: the keys, 33 calls that keep their
message in strict (and an unknown `touching`, which strict gives after
the command's name), the 13 stricter keys, Boss's schema against its
handler, and each feature's reply against FeatureResult.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Agents and scripts: Part Properties and Change Part Properties describe their exact parameters and reply (#12609)
component.metadata and component.metadata.set now publish typed input and
output schemas. Each parameter is read the way the handler reads it today and
refused with the handler's own message, and the replies are built from the
same types the output schemas describe, so the JSON they return does not
change. Their lines leave unconverted.txt.
Part of #2829.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* Change Part Properties: say plainly how `values` reads each key
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
selection.delete now publishes typed input and output schemas. Each
parameter is read the way the handler reads it today and refused with the
handler's own message, and the reply is built from the same type the output
schema describes, so the JSON it returns does not change. Its line leaves
unconverted.txt.
Part of #2829.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* Agents and scripts: Inspect Silhouette and Export View DXF describe their exact parameters and reply (#12611)
inspect.silhouette and file.export_view_dxf now publish typed input and
output schemas. Each parameter is read the way the handler reads it today
and refused with the handler's own message, and the replies are built from
the same types the output schemas describe, so the JSON they return does
not change. Their lines leave unconverted.txt.
Part of #2829.
Made with Claude Sonnet 5.5 (medium effort, fast mode off) through the Claude API, with Claude Opus 5.5 as advisor.
* Export View DXF: read the triangle count through the meshes module
The typed reply read `meshes.triangles` in `view_dxf.rs`, which
`cargo xtask mesh-decisions` takes for a model decision on a mesh. The
count is only reported in the reply's stats, so `view_dxf_meshes.rs`,
the module that builds the drawing's display meshes, now hands it out.
Made with Claude Opus 5.5 in Claude Code (Ultra plan, default effort, fast mode off, Ultracode off).
* claim: #12620
* Motion Study: Play runs on by itself at every speed (#12620)
The Step slider was given the playhead itself. It is an integer slider,
and egui rounds such a slider's value every frame, so playback's gain of
0.4 of a step a frame at 1x went back to 0: Play only moved when a mouse
move made a long frame. The slider now gets a rounded copy and writes
back only when it is moved.
advance() also passes the end once the step shown would be after the
last one, so a loop no longer shows "11 / 10" before starting over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The field is `Option<NameList>`, so its published schema allowed `null`
(from the `Option`) and an empty list (from `NameList`), both of which
the handler refuses ("`bodies` must list bodies"). It now has its own
schema: a name, or a list of 1 to 10 000 names. The reader is unchanged,
so strict still gives the handler's message for those calls. The schema
test adds both cases.
This is 9dfa1ade2 from #12314, pushed after review; the merge queue
merged #12314 from the commit before it, so main never got it.
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #12655
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Rib, Web, Remove and Boundary Fill: input and output schemas (#12655)
Typed contracts for solid.rib, solid.web, solid.remove and
solid.boundary_fill in a new schema/features_more.rs, wired with
checked!, .contract and .output; the four leave schema/unconverted.txt.
Each reply is the feature commands' FeatureResult. Rib and Web share
RibParams, as they share their handler.
Each key reads as its handler reads it, with its message: `curves`,
`bodies` and `tools` through NameList with the handlers' counts (null
read as empty, as params::string_list reads it), `cells` through
FillCells, `sketch` as IdOrName, lengths as LengthExpr, and the common
feature keys through FeatureCommon. Rib's `direction` is a new
keyword_param! type, RibDirectionParam. Rib, Web and Boundary Fill take
`touching`, as the other joining features do.
The contracts refuse keys of the wrong type that the handlers ignore
today: an item of `cells` that isn't a point (skipped today), and a
`flip`, `remove_tools`, `depth`, `direction` or `operation` of the wrong
type.
Tests in schema/features_more_tests.rs: the keys, 30 calls that keep
their message in strict, 6 stricter keys, 20 calls checked against both
schema and handler, and each command's reply against FeatureResult.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Boundary Fill: the contract refuses a null `cells`
`cells` is optional in the type so that a call without it gets the
handler's own message, but a `null` reached the handler too, so the
check passed a call the handler then refused. It now takes `present`,
so a `null` goes to FillCells, which refuses it with the handler's
message. `curves`, `bodies` and `tools` already refuse `null` and an
empty list; a new test pins all four, and that a key left out still goes
on to the handler.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #12668
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Motion studies: input and output schemas for Play Motion Study and Export Motion Study (#12668)
Typed contracts and reply types for motion.play and motion.export in a
new schema/motion_play.rs, wired with checked!, .contract and .output;
the two leave schema/unconverted.txt.
Each key reads as its handler reads it, with its message: `study`
through the motion study commands' reader and schema (now visible to the
module beside them), `step` and `samples` with their handlers' messages.
`study` and `step` are optional in the types and required in the
schemas, so a call without them keeps the handler's message.
The handlers build their replies from the new types through typed_reply.
drive_with_contact still returns JSON for its other callers; Play and
Export read its report into DriveReport. Export writes the same files as
before: the JSON through a Value (sorted keys), the CSV's step as JSON
writes it (`0.0`).
The contracts refuse a `quiet` that isn't true or false and a `path`
that isn't text, which the handlers ignore today.
Tests in schema/motion_play_tests.rs: the keys, 14 calls that keep their
message in strict, the two stricter keys, 13 calls checked against both
schema and handler, and the replies, including the JSON and CSV files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #12069
* wip: Sketch preference: a quick double Esc can be kept from finishing the sketch (#12069)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: profile key count includes the new sketch preference (#12069)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11978
* wip: Parameters filter and completion match the starts of a name's words (#11978)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: describe the Parameters filter's word-start matching in the guide (#11978)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters autocomplete: list word-start matches after substring matches (#11978)
A name matched only by the starts of its words now comes after every name that contains the typed word, so it never pushes an earlier suggestion out of the list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12208
* wip: Center Point Arc three-click regression test (#12208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Center Point Arc: keep the Radius box off the cursor so a click places the start (#12208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Arc click test: is_dimension is on the constraint's kind
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch value boxes: a box that would sit on the pointer moves beside it (#12208)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12543
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* wip: Insert Mesh origin option (#12543)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Mesh: name the Move to origin checkbox's handle mesh_units:origin (#12543)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Insert Mesh origin: measure in the active component's frame, first placement when it has no path (#12543)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12494
* German: call Look At "Senkrecht auf" instead of "Ansehen" (#12494)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* German: the navigation bar's Look At is "Senkrecht auf" too (#12494)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11057
* Sketch Dimension: the value box stays clear of the pointer when tall or in a small view (#11057)
Place the type-first value box by its drawn size and try the four corners around
the pointer; where none fits, keep the 30 px gap on one axis and slide along the
other, instead of clamping both axes back over the pointer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Mirror: a curve clicked while the mirror line is active joins the entities (#11700)
With entities preselected, the Mirror window opens on the empty Mirror line field.
A circle clicked then was refused without a word, so it was left out of the mirror.
The commands whose first input is the shared Entities list now take such a pick
there and stay on the input they were on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch modify: candidate stays the active input's pick; clicks fall back to Entities
sketch_modify_ui::candidate again answers only for the active input, so the
Mirror line still refuses curved and external references. The Entities fallback
for a click moved to pick_candidate, which Dialog::candidate_on uses.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4850
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: test Symmetry of two corners of one rectangle edge about the origin (#4850)
Already fixed on main by #4626; this guards it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n(es): catch-up con en.json del 2026-11-07 (507 nuevas, 23 cambiadas)
Catch-up with `en.json` at `871014f95`.
* i18n(es): el título REST es Apoyo, como la orden y la ayuda
---------
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
* docs: drop CollabCraft and craft-cloud from the browser runtime design
SolidCraft's cloud Open/Save is its own design. Removes the CollabCraft,
craft.quasarke.net, craft-cloud and craft-nextcloud references added in #1258.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* docs: remove the browser runtime design note
docs/web-runtime.md described crates that do not exist in SolidCraft
(craft-runtime, craft-tier-store, craft-worker-host, craft-gpu) as its plan.
Removes the note and the links to it, and the craft-libs section in plugins.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* web: share previews point at app.solidcraft.io
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11306
* Preferences: save and restore panel layouts by workspace (#11306)
* Preferences: keep layout keys out of the viewport and fix validation checks
* Preferences: save other settings when panel layout conversion is refused
* Preferences: keep layout key handling scoped and deduplicate web warnings
* fix: retain imported panel column widths
* fix: use shared panel preference controls and tokens
* test: cover custom-height rows in the UI gallery
* Render hotfix: speed up mapped GPU path setup on lavapipe (#12664)
* Render: show GPU shader preparation while a job waits
* Render: share emitter lookup and show shader preparation in its own window
* Render: preserve preparation progress and dialog keys
* Render: target the displayed job from shader preparation Cancel
* CI: bound the progressive GPU UI test process
* Add Solarized Dark theme
* Add attribution
* Make Solarized Dark pass the theme checks and register it in the new theme order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Theme filter test: count the new Solarized Dark theme
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>
* claim: #8969
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: Divide Curve places points at equal lengths along curves (#8969)
A new PointAlong constraint holds a point at its share of a curve's
length (measured along the exact curve), so the points follow edits.
sketch.point.divide places them on lines, arcs, circles, ellipses,
splines and conics; trim and break loosen them to point-on-curve.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Divide Curve: use the solidcore_record segment and vector types after the layer split
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Divide Curve: exempt sketch.point.divide from kernel cases and show PointAlong glyphs
PointAlong constraints now get a glyph on their point (the midpoint icon),
light their point and curve, and hold an end against Extend like
PointOnCurve does.
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>
* Claim #9901
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: the helical-chain and face-classification scaling tests count work, not time (#9901)
Both timed a small run and one four times larger and compared the times,
which load arriving between the two runs can fail. They now count the
work that would grow quadratically, through test-only counters: the edges
Mesh::tangent_chain looks at to extend a chain, and the triangles
SurfaceIndex::contains measures a point against. Four times the input
must cost at most eight times the count, as the time ratio allowed. The
counts are the same on every run (2248 -> 8998 edges, 80250 -> 421776
triangles). scales_linearly and its test go from both files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #12288
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render camera: input and output schemas for Set, Inspect and Frame Render Camera and Apply Render Camera (#12288)
The four commands that set, read, fit and apply the render camera get
typed contracts and replies, in a new `schema/render_camera.rs`, and
leave `schema/unconverted.txt`.
- `RenderCamera` is a saved render setup's camera: its keys, and the
bounds `RenderSetup::validate` keeps (1e9 mm, 1e6 radians, a positive
distance, 0 <= fov < pi). The reader decodes it as the handler does,
through a setup holding only that camera, so each refusal keeps the
handler's message. The bounds are in the input schema only: a reply
gives back the camera in the view, which Frame Render Subject can move
beyond them, so the output schema has the camera's shape alone.
- `camera` and `name` are optional in the types and required in the
schemas (a `require` transform), so a call without them still gets
the handler's own message; `#[schemars(required)]` doesn't do this
next to `serde(default)`.
- The handlers build their replies from the reply types through a small
`typed_reply`; the JSON is unchanged.
Tests check each camera and aspect case against both the schema and the
handler, keep today's messages in strict, and match every reply against
its schema in warn.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Sketch: a tangent to an ellipse or a conic touches it and never crosses it (#11535)
A tangent between a free-form curve (ellipse, conic, spline) and another
curve, with no shared end point, brought the sample gap nearest zero to
zero. Curves that cross have a zero gap too, so a line or circle could
cut through an ellipse while the Tangent still showed as held. The solver
now brings the extremum of the gap nearest zero to zero (refined between
samples by a parabola), which only a touch satisfies. Where the gap has
no extremum, as on an open curve running away, it keeps the old gap.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tangent: a monotone gap takes the end nearer zero, not a crossing's zero (#11535)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tangent: an open free-form curve touching at its end has no residual (#11535)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #11663
* Fix: a box selection with dimensions in it is fixed at once (#11663)
A box drawn over a sketch also takes its dimensions and constraint icons.
Fix started with that selection ignored all of it and fell back to the
pick tool, so nothing was fixed. It now passes over the dimensions and
constraint icons and fixes the boxed points and curves, up to the 10,000
names the command takes (the shared constraint limit of 200 no longer
applies to Fix).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix constraint test: a selection of only points gives each its own Fix (#11663)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Fix from a selection: a curve of another sketch goes to the pick tool (#11663)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Three ways a flat pattern whose edges come within 50 nm of a bend zone's edge failed:
- An outline with a vertex a hair from where a bend zone ends (a collinear vertex, as a DXF
split at its bend lines has) is split there, and the panel's loop keeps an edge that short.
SMLib's face builder refuses it ("SMLib failed (status 1001)"), so unfold fails. Such hair
edges are now merged into the line they run along.
- A cut-out whose edge stops a hair short of a bend line, or past it, or whose rounded corner
ends tangent a hair beside it, leaves a sliver face along the line, which the sew cannot tell
apart from its neighbour ("ambiguous edge contact"), so refold fails. Corners that close are
now moved onto the bend zone's edge (an arc moves with its centre); the cut moves under 0.05 µm.
- The planar booleans close a loop within 1 nm, but hand back free-form loops whose pieces are
up to tens of nm apart, which a following extrude or boolean refused. Gaps under 50 nm are now
welded before a loop is extruded or combined.
A cut-out that encloses no area is skipped.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #6404
* Joint symbols: a square for rigid joints, sized to the joined parts, faint where they pile up (#6404)
In an assembly with many joints the symbols covered the model: rigid
joints drew the same motion axis and ring as moving ones, and every
symbol was a fixed 5 % of the view height, so zoomed out a cluster of
joints swamped the parts.
- A rigid joint draws a small square, with no axis or ring. This covers
every rigid joint, including those joint.rigid_group saves.
- A moving joint's axis is at most 5 % of the view height, as before,
and at most a quarter of the smaller joined part's size (its bodies'
box diagonal, found once per design revision), so zoomed out the
symbols shrink with the parts.
- Symbols are planned picked first, then moving, then rigid joints; one
whose centre falls inside a symbol planned before it is drawn faint,
unless it is picked.
The planning is joint_glyph_plan, tested without painting.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FN5eEMrqWWXqi65FcK8GXq
* Joint symbols: hovering one names the joint and its type (#6404)
A tooltip on a joint symbol in the 3D view shows the joint's name and
type, e.g. "Glue · Rigid", so the square, ring and axis explain
themselves, as the timeline and browser entries already do. The pointer
picks the nearest symbol within 8 points; of symbols piled on one spot,
the one drawn on top. No tooltip while a mouse button is held.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMbqRqYimvY26QzcvrojSC
* Joint symbols: the tooltip names the type only where the name doesn't (#6404)
Review on #6742: "Rigid 7 · Rigid" said the type twice. A name that
already contains the type's label is shown alone ("Rigid 7"); any other
name is followed by the type in brackets ("Glue (Rigid)"). The type is the
Type box's label (kind_label, "Pin-slot" rather than "PinSlot"), translated
like the rest of the interface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMbqRqYimvY26QzcvrojSC
* Fix compile after the layer split
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the current egui id calls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Format the joint tooltip call
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: sascha2101 <221609842+sascha2101@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #4109
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: a preference to turn off scaling the sketch to its first dimension (#4109)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Test the first-dimension preference at small, typical and large values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Engine: the scale_sketch doc names the Sketch preference
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Translate the first-dimension preference into the complete catalogs (ja, zh)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add the Arabic, Hebrew and Vietnamese texts for the first-dimension preference
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>
Add saved optical models, reciprocal rough interface transport, hard-light
connections, and bounded photon caustics for surfaces and selected media.
Keep inactive Glass sampling behavior and document admission and model limits.
Expose native controls and provide original CPU/GPU diagnostic captures.
* Orbit drags keep turning at the edge of the view
An orbit drag that reached the edge of the screen stopped turning: the cursor
stops there, so the pointer reports no more motion. Now, when an orbit drag
reaches the edge of the view, the cursor moves to the opposite edge and the
frame that reports the jump turns the view only by the hand's motion, so the
drag can go round the model as far as the user likes. Synthetic drags
(control channel, scripts) never move the real cursor. Where the cursor can't
be moved (Wayland, the browser) nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Orbit cursor wrap: a Navigation preference keeps the old behaviour
Wrapping the cursor during an orbit changes what an orbit drag does out of the
box, so Preferences › Navigation gets "Wrap the cursor while orbiting" (on by
default, `orbit_cursor_wrap` in ui.prefs). Off, wrap_cursor never moves the
cursor and the drag stops at the edge of the screen as before. The new strings
are a pending locale file, with Arabic, Hebrew, Japanese, Vietnamese and Chinese
for the complete catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Use the current egui id calls
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: rootarchitect <7781866+rootarchitect@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Hole: read the faces nearest its position, not every face of the design
`hole_direction_at` classified the surface of every face of every body
(`faces_through` with an infinite reach) and then used the nearest one,
nearly always the first. In an assembly of hundreds of imported parts that
was most of a hole's time, on every preview and apply, and the viewport's
hole manipulator calls it too.
- Faces are now sorted by their mesh distance first (cheap) and each surface
is classified only when the direction rules reach it, in the same order, so
the direction is the same.
- `hole_on_surface` skips meshes whose box the drilling line does not meet
(it only casts along that line), and `faces_through` skips meshes farther
than its reach.
A cost test counts the surfaces classified for one hole among forty parts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hole: fix the new test for the references field
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Rename the faces_through line in mesh-decisions to faces_near
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>
* claim: #12293
* Test: a parameter read only by a sketch point lists the sketch in used_by (#12293)
* Parameters: used_by names the feature that reads a parameter itself, as the graph does (#12293)
parameters.list built used_by from named parameter expressions only, so a
parameter read by a sketch point coordinate, a sketch pattern or a sheet
metal or plastic rule listed no user. The names a feature reads through its
own expressions now come from one helper that both parameters.graph and the
parameter table use.
---------
Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
* claim: #12191
* Test: a hole picked at both ends gets one fastener at its open end (#12191)
* Insert Fastener: picks on one hole seat one part, at the hole's open end (#12191)
Picks were merged only when they seated at the same point, so a hole picked
at both ends (a blind hole's rim and floor edge, a through hole's two rims)
got a second fastener, turned over. Picks on one axis line of one body now
seat one part. When one of them is a blind hole's floor edge, the part is
seated at the rim instead: hole_wall now says whether the picked end of the
wall opens onto air, by classifying a point on the axis just past it.
* Insert Fastener: coaxial blind holes from opposite faces stay two holes, a fastener each (#12191)
---------
Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
* Point clouds: remember the source file and reload it (#8818)
A cloud keeps the absolute path it was inserted from. pointcloud.reload
reads it again, keeping the cloud's name, placement, units and colouring,
and the filters on properties the file still has; the Edit Point Cloud card
has a Reload from file button.
The file is read beside the window through the file loader, with progress
and Cancel, as an insert is. A thinned cloud stays thinned to as many points,
which reads an unchanged file as the same points; any other is read as a new
insert would be, so a file that grew comes in whole.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload asks before it reaches a network location (#8818)
A design names the cloud's file, and a design may come from anyone. On
Windows, only asking about `\\host\share\x` connects to that computer and
may send it the user's sign-in. So `pointcloud.reload` now reads through
`vfs::read_design_path`, as linked designs do, and Reload from file asks
with the same prompt as Update Links.
The button's label uses its stable message id. The strings gain Korean,
which a new label needs. `pointcloud.reload` gets its line in
tests/kernel-cases/EXEMPT, which the "Every command has a case" job needs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload reads under the limit the cloud was read under (#8818)
The limit was inferred from the counts, as if a cloud with fewer points than its
file had been thinned. A point that cannot be read makes the same difference:
such a PLY read as half its points on an unchanged file, since a limit one below
the file's count is a stride of two, and a file that grew was cut to the old count.
Keep the limit itself: a read is a function of the file and the limit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload drops a value filter when the file stops naming the values (#8818)
A set of values filters a label, and `pointcloud.edit` refuses one on a property with no names.
A reload kept such a filter while the property stayed, so a file that dropped its names comment
left a cloud every later edit refused. One predicate now says what is refused and what a reload
drops.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a reload keeps its undo step and ends a play it moved (#8818)
A reload accepted with the Edit Point Cloud card open left the card's undo
boundary before it, so the card's next edit folded the reload into itself and
one Undo took back both. The boundary now moves past the reload.
A play told its filter by place alone. When a filter before it went, by a
reload, an agent's edit or an undo, the next filter took that place and the
play's range, on screen and written when the play ended. A play now names its
filter's property and ends, unwritten, when that place holds another.
`engine.batch` and the control hook compared command ids exactly, while the
engine takes any case: `POINTCLOUD.RELOAD` passed the refusal and read the file
on the window thread. Both now resolve the id as the engine does, for every
command that loads a file. The batch row of the protocol said the refusal held
only with the agent cursor shown; it always holds.
No test was run locally, at the owner's request: compile, clippy and fmt only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a file load keeps the request's own param_check (#8818)
`file_load::control` takes a command the file loader runs before
`control::handle` does, so that command skipped the request's `param_check`:
`pointcloud.reload` accepted a word that is no level, a `strict` call with an
unknown parameter read the file anyway, and the reply did not say the level.
`doc.open`, the imports and the DXF export went the same way.
The hook now takes the level itself and checks the call before the file is
touched, as a command that switches designs does: the worker's session goes on
to be the design's, which keeps its own level. Both share the same check, and
the reply says the level and any warnings.
No test was run locally, at the owner's request: compile, clippy and fmt only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Rebased onto main as one change: the joints group into one button in the timeline, and the History bar folds into component runs, feature folders and STEP inserts. Both views are Preferences > Layout settings and off by default.
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #11741
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render bindings: input and output schemas for the surface and optical bind and unbind commands (#11741)
describe_command now gives the exact parameters and reply of the four
commands that bind a library surface or an optical profile to bodies in
a saved render setup, and unbind them, and the warn and strict
parameter checks use them. The handlers already read their parameters
with strict serde types; those types move to the new
schema/render_bindings.rs, gain descriptions and JsonSchema, and become
the contracts. The doc crate's target and setup types have no
JsonSchema, so their schemas are written by hand. The replies are built
from typed values instead of json! literals, with the same JSON, and
the saved setup is serialized before the document changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render bindings schema: `set` names the setup's fields, and `origin`'s default is the body's centre
`patched_settings` reads a `set` patch onto the saved setup as a
RenderSetup, which refuses unknown fields, so the schema now names the
setup's 14 fields with additionalProperties false (still at most 13 at
once). Without `surface.origin`, render.library.surface.prepare starts
the maps at the centre of the body's bounding box, not at the origin;
the description now says so. A new test covers the field names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render bindings: keep main's CPU and GPU wording and the OpticalMaterial import after the rebase
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Agents and scripts: the view navigation and layout commands describe their exact parameters and reply (#12278)
describe_command now gives the exact parameters and reply of view.navigate,
view.layout, view.history_layout, view.graphics_preset and
view.ambient_occlusion, and the warn and strict parameter checks use them.
The handlers still read their parameters from the raw JSON, so the new types
read each key as its handler does and give its message. Keys the handlers
refuse when left out (mode, preset, enabled, camera) are required in the
schema but left to the handlers' messages. The replies are built from typed
values instead of json! literals, with the same JSON as before.
* view.navigate: the contract reads only the keys of the form the handler takes (#12278)
The handler reads a motion's keys when motion is given (each motion its own), else direction, else degrees and action, and never the keys of another form. The contract is now read by hand in the same order, so a key of another form is not checked in warn or strict either, and a call that works with the check off works in both.
---------
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #8923
* Sketch palette: drag it by its header to move it (#8923)
The palette docks at the right edge as before; its status header now drags it like a
command dialog's title, and it keeps its place for the rest of the session. It stays on
screen and never goes so low that Finish scrolls out of view. A wide dialog docked at
the left no longer shrinks to the sliver left of a palette that was moved there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: drop the token the palette's room no longer needs (#8923)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch palette: keep the token binding the dialog still uses after the rebase
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Design tabs: double-click a tab to rename the design
A double-click on the selected design's tab opens a name field in the tab
with the name selected, so typing replaces it. Enter or a click anywhere
else keeps the new name; Escape keeps the old one. The tab grows with the
name while it is typed (up to the tab's usual 200 pt). The rename goes
through component.rename with component 0, so it is undoable, marks the
design unsaved and is what Save As offers next. A name left as it was or
left blank changes nothing, and names are cut to the 128 characters the
command takes.
On desktop the bar is the window's title bar, and a double-click on a tab
used to maximize the window. Double-clicking the bar's empty space still
does; the tabs now rename. The preference tab_double_click_renames
(Preferences > Interface, "Double-click a design tab to rename it") brings
the old behaviour back.
Changes a default: yes - a double-click on a design tab renames the
design, old behaviour (maximize the window) kept behind preference
tab_double_click_renames.
Strings in assets/locales/pending/design-tab-rename.json (en, ar, de, he,
ja, ko, vi, zh). Docs: the UI tour and the preference list in
control-protocol.md (handle tab:rename).
Tests in documents.rs: Enter renames and undo restores; a click elsewhere
keeps the name and the tab grows while typing; Escape, a blank name and
the same name change nothing; an over-long name is cut to fit; with the
preference off a double-click does not rename.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix the tab rename for the current egui and ignore the rename result
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Single holes on several faces send one solid.hole command per face, each
carrying the same `name = expression` text. The second command was refused
because the name was already taken. A definition typed again exactly as an
existing user parameter was defined (same expression, same unit) now names
that parameter; a different value is still refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Loading a design walked every folder's chain of parents with a linear
search per step, so the check grew with the cube of the folder count: a
588 KB design with 10 000 nested folders took over three minutes to
open (also as a recovery autosave or over the control channel). Each
chain is now walked once through an id -> parent map, stopping at a
folder already known to end at the top. Cycles are still refused.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #12354
* Tests: agent_chat_home_page scrolls Add agent into view before clicking it (#12354)
With an AI agent installed, its card on Preferences > Assistant pushes Add
agent below the visible page. The scenario clicked the button's handle
point, which then lies on the empty footer, and timed out waiting for the
dialog. A widget step scrolls the button into view first.
---------
Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
* claim: #12270
* Test: run --out writes meshes at the --quality asked for (#12270)
* Command line: run --quality standard|fine|finest sets the mesh quality of --out exports (#12270)
file.export already took quality, but run --out passed the path alone, so a
fine STL needed a script. --quality is checked before anything runs and goes
to every --out export; the usage text and the getting-started guide say so.
* Command line: run refuses --quality given twice, and the docs say the mesh chord never goes under 1 µm (#12270)
---------
Co-authored-by: Ahmad Khan <ahmad.khan@shiritai.co>
#12171 changed the reorder refusal to "moving Notch to position 0 breaks
Notch (...)", so the history case that expected "moving Notch there breaks
Notch" now fails in the kernel rig on every pull request that runs it.
The case checks a substring, so expect "breaks Notch", which both wordings
contain.
* Render: restore saved shots, progressive images and exports
* Render: register existing live and coat command tests
* Render: isolate lens saves and retain bounded previews
Save only lens parameters on existing CPU/GPU jobs while keeping unrelated
draft settings dirty. Retain pinned checkpoints through preview and status,
and release host-only snapshots outside the status lock.
Use the admitted PNG bound for registered-executor checkpoint slots. Preserve
executor work credits, final publication and regrade ownership. Add regression
coverage and align help, translations and capability documentation.
* Render: preserve live feedback and address review regressions
* Preserve saved cameras when applying Render library edits
Keep the saved camera when lighting or maps are applied to an existing shot. Fit and centre the measured Render host using the painted picture. Carry forward the upstream CPU and GPU preset and ZoomTool contracts.
* claim: #12041
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* Plane at Angle: use axis direction only with a pivot (#12041)
Add a direction_only option that projects the axis onto the base and
turns about a pivot (or the base origin), with dialog checkbox and
conditional Pivot pick. Older designs keep turning about the full line.
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* i18n: add ar, he, vi for Use axis direction only (#12041)
Complete catalogs need every English message; the pending locale file
was missing those three so cargo xtask i18n --check failed.
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: ReDead <ReDead@users.noreply.github.com>
* Sketch Break and Extend: tests for a cut or stop on a center rectangle's centre
Breaking a center rectangle's diagonal at the centre leaves two points there, and extending a line up to the centre ends it on one diagonal only. These tests fail on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Break and Extend: a cut or stop on a held midpoint ends there, as Trim does
Break dropped the broken line's Midpoint and joined the pieces at a new point held only on the cutter, so a center rectangle's centre came loose and a corner drag pulled the two points apart. It now uses Trim's rule (one helper, hold_cut): the pieces meet at the held point, which moves its hold to the cutter's middle when the sketch already implies that, and otherwise stays on the cutter.
Extend stopped on a point it lays on the curve it meets unless that curve ends there. A point held at another line's middle (a center rectangle's centre) is now a stop too, so the extension joins it.
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>
* claim: #2364
* Sketch Dimension: an origin axis can be picked as a reference (#2364)
The dimension tool ignored clicks on an origin axis and the engine's
sketch.dimension did not resolve x/y. Axis picks now map to the sketch's
linked axis line, as Symmetry and the other line constraints already do,
in all three entry points (click-then-pick, select-then-D, and the command).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Dimension: tests for a circle centre and a line against the sketch axes (#3137, #3320)
Folds in the cases from #3298: a circle's centre point dimensioned to the
x / y axis (#3137), and a line's angle and an end's distance to the x axis
(#3320). The axis resolution from #2364 already covers them; a plain circle
or arc with a line still measures to its edge (#1803).
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Dimension: the axis test picks the line off its midpoint, which is a point pick (#4872)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Fillet, Chamfer, Offset Face (and Press Pull on a curved face, which opens
it), Draft, Replace Face and Delete Faces sent only the picked points. The
feature then checked every body's edges for them and evaluated on the body
whose mesh box is nearest the first point (`body_at`):
- In an assembly, a bore or pin inside a housing that comes first in the
body list was looked for on the housing, which has no face there, and the
command failed with "no face at [...]".
- In a large design, `body_at` meshed every body (and Fillet/Chamfer worked
out every body's edges) on each preview and apply: seconds per change.
These dialogs now name the body when every pick lies on one, as Shell does
since #4555; picks on several bodies name none, as before. Press Pull passes
a given body on to Offset Face.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7476
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Create Sketch: the flat faces of a cone take an attached sketch (#7476)
The cone primitive was evaluated as an unnamed tool, so its faces had
only geometric names, which a face attachment refuses as not exact.
Its faces are now named from the revolve that builds it, as a
cylinder's are: `bottom`, `side` and, on a cut-off cone, `top`. A
sketch on its base or top attaches by that history name and follows
the cone's height.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix compile after the layer split
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>
* claim: Mesh export: mesh bodies in parallel and weld seams through a grid
* Mesh export: mesh bodies on threads and find seam splits through a grid
STL, OBJ and 3MF exports meshed and welded one body after the other on
one core. Each body is now meshed (and welded) on its own thread with
map_on_threads, as the model cache encodes them; results stay in body
order and every body is welded on its own, so the files are the same,
byte for byte. Of several failing bodies the first one's error is the
export's, as before.
The search for vertices lying on open sides (T-junctions) looked at
every open-side vertex for every open side. It now bins those vertices
on a grid and looks only in the cells around each side; the vertices it
finds come out in the same ascending order, so the splits are the same.
A side whose box spans more cells than there are vertices, or that is
not finite, still looks at all of them. A test compares the result with
the previous all-pairs search (kept in the test module) on a cube meshed
at six resolutions per face with jittered vertices and broken triangles.
ASCII STL and OBJ write each line into the text instead of formatting
a string per line and appending it (same bytes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Fix compile after the layer split
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>
canvas.insert, canvas.decal, canvas.edit, canvas.calibrate and canvas.delete
now publish typed input and output schemas. Each parameter is read the way
the handler reads it today and refused with the handler's own message, and
the replies are built from the same types the output schemas describe, so the
JSON they return does not change. Their lines leave unconverted.txt.
The plane check of sketch.create's plane reader is split out so the canvas
contract can use it without changing what it accepts.
Part of #2829.
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: sketch font is included in vendored sources (#12162)
Co-Authored-By: Codex <noreply@openai.com>
* fix: include the sketch default font in vendored sources
Co-Authored-By: Codex <noreply@openai.com>
---------
Co-authored-by: Codex <noreply@openai.com>
* claim: #12313
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Plastic rules: input and output schemas for Manage, Assign and List Plastic Rules (#12313)
The three plastic rule commands get typed contracts and replies, in a
new `schema/plastic.rs` laid out like the sheet metal rules', and leave
`schema/unconverted.txt`.
- Each key reads as its handler reads it, with the handler's message:
the rule name as `plastic.manage_rules` trims and limits it, `bodies`
through `config::NameList` (which mirrors `params::string_list`), the
ten rule values as `LengthExpr` and `AngleExpr`.
- `name` and `bodies` are optional in the types and required in the
schemas (a `require` transform), so a call without them still gets
the handler's own message.
- Keys of the wrong type that the handlers ignore today (a non-boolean
`active` or `delete`, a rule value that is neither a number nor text,
a non-text `from`, `material` or `rule`) are refused; in warn the call
still works, with a warning. A test pins each one.
- The handlers build their replies from the reply types through a small
`typed_reply`; the JSON is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assign Plastic Rule: the schema's `bodies` is a name or a non-empty list, not null
The field is `Option<NameList>`, so its published schema allowed `null`
(from the `Option`) and an empty list (from `NameList`), both of which
the handler refuses ("`bodies` must list bodies"). It now has its own
schema: a name, or a list of 1 to 10 000 names. The reader is unchanged,
so strict still gives the handler's message for those calls. The schema
test adds both cases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
component.insert_design, update_links, relink, break_link, links, extract,
copies and replace_with_link now publish typed input and output schemas.
Each parameter is read as its handler reads it today, with the same error
messages, and each reply is built from the typed value and serializes to the
same JSON as before. Their lines leave unconverted.txt.
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #11039
* Insert SVG: shapes a file only defines or hides are not imported (#11039)
read_svg drew every shape it met, whatever its parents were, so a circle
inside defs, clipPath, mask, symbol, marker or pattern, or one hidden by
display: none, became a sketch curve a browser never shows (and could
punch a hole in an extrude). Those elements and their contents are now
skipped; the stack still follows the end tags, so what comes after reads
as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* The command reference: the parameters as a table, with their type and whether they are required (#5237)
The command reference shows each command's parameters as one raw line of
registry notation, so a reader has to read it as code to learn what a
command takes. The generated page now lists the same parameters as a table:
the name, the type, whether the command needs it, and what the registry
says about it. The complete parameter text stays in the automation details,
so nothing is lost for the scripts that read it.
The table is generated from the command registry, like the rest of the page,
so it cannot drift from the commands. The notation is read by a small parser
that knows what the registry writes: names written apart share the type
that follows them, a question mark marks a parameter optional, a pipe
between names offers alternative names for one parameter, a pipe after the
colon lists the types it accepts, a parenthesis says what the parameter
does, and the text after an arrow is the reply the command gives, not a
parameter.
Co-Authored-By: Claude <noreply@anthropic.com>
* test: the parameter table keeps its description literal
The parameter table shows registry text, so a description that carries
inline Markdown must be shown literally: backticks, emphasis and links
are characters, not syntax.
The type of a parameter ends where its sentence ends, so the sentence
that follows it is not part of the type.
Both tests fail on the current code.
* fix: render the parameter description as prose
The description cell of the parameter table is registry text, and the
rest of the file already renders registry text with prose(): backticks,
emphasis and links are characters, not syntax. table_cell() only kept
the table and the page intact, so a description like `#name = expression`
came out as a code span and a description with `*bold*` came out as
emphasis.
The name and the type keep table_cell(): they are identifiers and
notation, and they are not Markdown prose.
* fix: cut the parameter type at the end of the sentence
A type runs to the end of the clause, so the sentence that follows it
belongs to the command, not to the type: `construction?: bool. Returns
sides {bottom, right, top, left}` showed the whole sentence as the type
of `construction`, and the description of `sides` with it.
The type now ends at a full stop followed by a space and a capital
letter. A dotted or multiword type such as `sketch.rectangle` or
`number or expression` does not match that pattern, so it keeps its
current handling.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #11941
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* User parameters: input and output schemas for Add, Rename, Delete, Comment, Favorite and Move (#11941)
describe_command now gives the exact parameters and reply of the six
commands that manage a design's user parameters by name, and the warn
and strict parameter checks use them. The handlers read their
parameters from the raw JSON, so the contracts in the new
schema/parameters.rs read each key as its handler does, with its
message; `value` stays as parameters.add's other name for `expression`,
and `comment` is required in the schema but left to the handler's
message in the type. The replies are built from typed values instead of
json! literals, with the same JSON; parameters.add's reply flattens an
untagged AddedValue (a number's value or a text parameter's text).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameters: the schemas count a `null` expression and Move's neighbour as left out
`parameters.add` reads a `null` `expression` or `value` as left out
(`expr_arg`), so the schema's "expression or value" alternatives now
also need the key's type (number or text): `{"expression": null}` alone
no longer matches, while `{"expression": null, "value": 3}` does.
`parameters.move` needs exactly one of `before` and `after`. The schema
now says so with a `oneOf` whose alternatives each need their key as
text, so a `null` neighbour (read as left out) counts as neither, and
both or neither is refused. `conforms` doesn't read `oneOf`, so the test
pins it and counts the matching alternatives itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
- timeline.redefine maps a surface revolve's {origin, dir} axis through an edit-only table entry
- a new surface revolve still goes whole through to_active_frame (picked edge and face axes)
- tests: made and edited in a moved and a turned component against the solid revolve; a picked edge axis
Co-authored-by: P'tite Tête <asso@ptitetete.com>
* claim: #12155
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Motion studies: input and output schemas for Motion Study, List and Delete (#12155)
describe_command now gives the exact parameters and reply of
motion.study, motion.list and motion.delete, and the warn and strict
parameter checks use them. The handler reads its parameters from the
raw JSON, so the contracts in schema/motion.rs read each key as it
does, with its message (a study by id or name, steps, and keys of
[step, value] points); whether the study, joint and value exist stays
the handler's to check. The replies are built from typed values
instead of json! literals, with the same JSON, through `typed_reply`
(the contact commands' helper, renamed now that it serves both);
motion.list's traces stay as motion_trace.rs builds them.
The parameter audit found two tests in cmd/motion_tests.rs sending
`delete: true` to motion.delete, a key only Edit Contact Set takes; a
`deletion` helper now sends it only there.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Motion Study: a point's step must be a number in the schema, as the handler reads it
The schema typed both elements of a key's point as "a number or text",
so `["0", 20]` matched it although the handler refuses a step that isn't
a number ("points are [step, value]"). The two are now typed apart with
`prefixItems`: the step a number, the value a number or an expression,
each under 1e9 in size, as `study_key_problem` and the handler read them.
Elements after those two, which the handler ignores, are left free.
The 1e9 limit, written twice in the key check, is now the named
`MAX_POINT_MAGNITUDE`, used by the check and the schema alike. A new test
checks nine point shapes against both the schema and the key check, so
the two can't drift apart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #12222
* Web app: clippy for wasm passes with -D warnings (#12222)
cargo clippy -p solidcraft-web --target wasm32-unknown-unknown stopped at
drop(file) in safe_write.rs and showed 12 more warnings only that target
has. Each is fixed where it is:
- safe_write::create_atomic and control_auth::write_token_file_with close
their File by scope (a block; Result::map's closure) instead of drop(),
which clippy's drop_non_drop flags where File is a stub.
- Native-only imports, the AHEAD constant and the point-cloud worker's
Condvar are behind cfg(not(target_arch = "wasm32")); Exported::bodies
and Start::Later, read or built only natively, allow dead_code there.
- home::ask_present uses ctx in its browser branch; the browser blur stub's
begin returns a Scope, as the native one does.
- The web app drops three map_err(JsValue::from) on dyn_into's error,
which is a JsValue already.
Native builds behave the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
browser.collapse_all, browser.expand_all, browser.group, browser.move,
browser.order, browser.rename_group, browser.ungroup, browser.visibility
and body.rename now publish input and output schemas, so agents and
scripts can read exactly which parameters they take and what they
return, and the warn and strict parameter checks cover them.
Each parameter is read the way the handler reads it today, with the same
error messages. The replies are built from typed values and serialize to
the same JSON as before. Part of #2829.
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* Extract a component whose body was built in another component
component.extract now takes along the outside features that built, changed or moved
in a body the component owns, when they touch nothing that stays outside. Otherwise
it names the feature that blocks it. Fixes#12142.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Kernel case: extracting a component with a body moved in now succeeds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11766
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render presets: input and output schemas for the native studio and quality profile commands (#11766)
describe_command now gives the exact parameters and reply of
render.preset.resolve and render.preset.apply (review and apply a
native studio composition) and render.quality.prepare (review a fixed
sampling budget), and the warn and strict parameter checks use them.
The handlers already read their parameters with strict serde types;
those types move to the new schema/render_presets.rs, gain descriptions
and JsonSchema, and become the contracts. The replies are built from
typed StudioComposition and QualityPlan values instead of json!
literals, with the same JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render presets schema: `set` names the setup's fields
As on #11762 and #11742: `patched_settings` reads a `set` patch onto
the saved setup as a RenderSetup, which refuses unknown fields, so the
schema now names the setup's 14 fields with additionalProperties false
(still at most 13 at once). A new test covers it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #11862
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Physical materials: input and output schemas for New, Edit, Inspect and Delete Physical Material (#11862)
describe_command now gives the exact parameters and reply of
material.create, material.edit, material.inspect and material.delete,
and the warn and strict parameter checks use them. The handlers read
their parameters from the raw JSON, so the contracts in the new
schema/materials.rs read each key as its handler does, with its
message: `null` keeps its meaning (`color: null` and `physical_card:
null` clear), the look keys reuse the appearance commands' types, and
the card keeps the handler's own check, with its schema from
schema/material_properties.rs (whose card_schema becomes pub(super)).
The replies are built from typed values instead of json! literals, with
the same JSON.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Colour schema: a colour's text form allows what color_param reads
inspect::color_param trims the text and drops any number of leading
`#`, so " #ff00ff " and "##ff00ff" are colours to appearance.assign
and the material commands, but ColorParam's published pattern refused
them. The pattern is now built from schema::types' whitespace class (as
AxisDir's is), which becomes pub(crate), and a test pins it next to the
reader's answers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Colour pattern: a byte may be `+` and one hex digit, as the reader takes it
`inspect::color_param` parses each of the three bytes with
`u8::from_str_radix`, which accepts a leading `+`, so "+fff00" is read as
[15, 255, 0] today. The schema's pattern asked for six hex digits and
would have made strict refuse a colour the handler accepts. The pattern
now allows either two hex digits or `+` and one digit per byte, and the
test pins both the reader's answer and the pattern's text, since
`conforms` doesn't read patterns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #11761
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render HDR: input and output schemas for Inspect HDR Source and Save Render Settings with HDR (#11761)
describe_command now gives render.hdr.inspect's and
render.settings.capture_hdr's exact parameters and reply, and the warn
and strict parameter checks use them. The handlers already read their
parameters with strict serde types; those types move to the new
schema/render_hdr.rs, gain descriptions and JsonSchema, and become the
contracts, so the check is no stricter than the handlers. The replies
are built from typed HdrInspected and HdrCaptured values instead of
json! literals, with the same JSON. `expected_sha256` is the doc crate's
HdrId, whose schema is written by hand (64 lowercase hex digits or null).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Render HDR schema: `set` names the setup's fields, and its job may be the saved one's
`patched_settings` puts a `set` patch's keys onto the saved setup and
reads the result as a RenderSetup, which refuses unknown fields. The
schema now names the setup's 14 fields with additionalProperties false
(still at most 13 at once), so `set: {"widht": 10}` is refused, as the
handler refuses it; the values stay the handler's to check. The
description now says the setup the changes make (a saved one keeps the
fields left out) needs a cpu_raytrace or gpu_path job, not that every
patch must give one. A new test covers the field names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* claim: #11375
Co-authored-by: Cursor <cursoragent@cursor.com>
* Web: tab title follows a renamed design (#11375).
When an OPFS design is renamed, open tabs keep the new path and a borrowed design name so the tab matches the file list.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* claim: #11088
* test(ui): cover Paste New into a selected parent
* test(ui): use SolidCore Vec3 in clipboard regression
* test(ui): verify copied face attachment through menu
* style(ui): apply clipboard test formatting
---------
Co-authored-by: ermanyop-stack <278786764+ermanyop-stack@users.noreply.github.com>
Document::placements scanned every occurrence for each component it visited,
so its cost was placements times occurrences, on every world placement. The
new OccurrenceTree (crates/doc/src/occurrence_tree.rs) lists each component's
children once, in one pass and in document order; placements walks it with
the same order, caps and stop behaviour, through placements_with, which takes
each occurrence's transform from the caller. inside_component reads the same
index instead of scanning per component.
Tests compare the walk with the scanning one on 13 shapes and on hostile
generated documents, at its caps, in document order and with a given
transform. Release, 97,540 placements: about 120 ms -> 15.5 ms, the same in
either run order (docs/perf.md).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* timeline.reorder: say when nothing moved, and return the new index (#12140)
A position past the end was capped to the last slot; when the feature was
already there the command reported success with no sign nothing moved. The
reply now gives position (index now), from and moved, with a note that says
why nothing moved or that the last slot was used. A move that puts a feature
ahead of what it uses says so, and the command refuses before changing the
design if the feature would not land at the asked position.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* timeline.reorder: word a refused move by its direction; note a marker-only change
A feature moved later can fail for reasons other than order, such as a cut that
removes its body, so the move-earlier hint is given only when it moved earlier.
With `active`, a feature left in place gets a note saying only the history
marker moved, or that nothing moved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: offline script marker diagnostics for #3956
Co-Authored-By: Codex <noreply@openai.com>
* Report script build markers in offline web test timeouts
Expose the existing started and shared dataset markers in diagnostic state. Preserve missing markers as null without changing readiness or retry behavior.
Co-Authored-By: Codex <noreply@openai.com>
---------
Co-authored-by: Codex <noreply@openai.com>
* Arabic: one spelling of component in the Browser rows start closed note, dialog wording in the tab-close tip
* Arabic: no direction marks in the MCP compact output note
* Arabic: general term for the surface grain angle, Output name in the image import note and the Output inspector header
* Arabic: one word for the mouse in the right-click notes of the intersection curve pick and the Play History button
* Arabic: bridging term in the Hole dialog, intersection curve help pronoun, ground shadow tip wording
* Arabic: pan term in the drag-keeps-mode help and the vertical pan direction option
* Arabic: Move Sketch Points name, Move tool name in the drag highlight preference, file format wording in the newer-file notices
* claim: Parameter Sweep's schema requires parameter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Parameter Sweep: the schema requires `parameter`, as text
`inspect.parameter_sweep` refuses a call without `parameter`, or with a
`null` or numeric one ("`parameter` is required (parameters.list)"), but
its published schema (#10978) neither required the key nor ruled out
`null`: the field's `#[schemars(required)]` has no effect on a key with
`serde(default)`, and `Option<String>` publishes `["string", "null"]`.
The contract keeps the key optional in its type, so the handler, not the
check, gives its own message when it's left out; the schema now requires
it through a struct-level transform, as the contact commands do, and
types it as a string. A new test checks the schema against a call
without the key, with `null` and with a number; it fails without this
change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Sketch modify: name the public command in the split and trim errors (#4712)
Part of #4712.
The split inside the sketch modify tools refused in the name of the
internal `split` step, and the free-form trim in the name of the
internal `trim` one: `invalid parameters for `split`: a circle needs
two points to split at` names a command nobody can write, so an agent
or script reading the message has nowhere to go.
Both now name the public command the caller used: `sketch.modify` for
the split shared by trim and break, `sketch.trim` for the trim. The
descriptions are unchanged.
Co-authored-by: Hermes Agent <noreply@anthropic.com>
* Sketch modify: the split names the command the caller used (#4712)
`sketch.modify` is not a command a caller can write: the registered
sketch commands are `sketch.trim`, `sketch.trim.path` and `sketch.break`.
The split shared by trim and break therefore refused in the name of a
command that does not exist, and a script reading the message had
nowhere to go.
`split_at` takes the command of its caller and refuses in that name:
`sketch.trim` when the trim splits the curve, `sketch.break` when the
break does. Both callers already had the command in hand.
The test now states the decision: the refusal of `sketch.break` carries
`sketch.break`, and the rendered text names no other command.
Co-authored-by: Hermes Agent <noreply@anthropic.com>
---------
Co-authored-by: Hermes Agent <noreply@anthropic.com>
* Extrude: Two Sides keeps each distance on the side Direction gives it
Distance 2 stays on the side Direction gives it, whatever the sign of Distance 1, and its value box shows on the canvas. Rebased onto main as one change.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Two Sides extrude: keep older designs' sides by a marker, and the full profile search
- A saved two-sided extrude is marked `second_follows_first` instead of having a literal
negative distance rewritten: expressions such as `-width` keep the old sides when their
parameters change, `-0 mm` keeps its span, and a feature's named inputs keep their values.
- Live sketch drags and the restored model cache use the full profile search evaluation uses,
so a sketch whose only closed boundary is projected keeps its profiles.
- The canvas Distance 2 box stays while its text does not evaluate.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: give the extrude added on main the second_follows_first field
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Appearance: theme picker with carousel, grid, filters and Day/Night slots
* Appearance: theme picker arrows move a screenful of themes
* Theme picker: translations, token metrics, wheel helper in navigation, reveal tolerance
* Theme picker: keep both new Appearance preferences after the rebase
* Appearance: adapt theme picker to egui 0.37
* claim: #2398
* Extrude: the distance arrow stops at a parallel face and outlines it (#2398)
While Extrude's distance arrow is dragged, a flat face parallel to the
profile and lying over it takes the distance exactly once the tip comes
within 8 screen points of its plane, and the face is outlined as a hovered
face is. A cut dragged through a block so stops at its far side. Faces
beside the profile don't catch the arrow; Alt/Ctrl (free drag) turns it off.
The distance stays a number in the existing parameter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Extrude face stop: clippy (test-only current, +=)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Extrude face stops: a preference, on by default, keeps the old round steps (#2398)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Extrude face stops: follow world_state returning a Result (#2398)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Extrude face stops: import Vec/Plane from solidcore_record after the layer split
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>
* joints: support custom names in dialog and type-based default names
Allow users to specify a custom name in the Joint dialog and generate
type-prefixed default names (e.g. "Planar Joint1", "Revolute Joint1")
instead of generic "Joint{n}".
- Add `name` field to `JointForm` and display a text row in the Joint
dialog with a dynamic placeholder matching the chosen type.
- Update `new_joint_name` in `cmd/joints.rs` to take `JointKind` and
prefix auto-generated names with the joint kind label.
- Prioritize non-empty custom names and pass them through `joint.create`,
`joint.as_built`, and `joint.edit`.
- Pre-populate the existing joint name in `edit_joint` to allow renaming
directly from the Edit Joint dialog.
* joints: add tests for custom and type-based joint naming
Verify that the joint dialog and engine command layer correctly generate
type-prefixed default names and respect user-specified custom names.
- Add engine-layer test `new_joints_generate_type_prefixed_names_or_keep_custom_name`
to verify `joint.create` and `joint.as_built` naming logic.
- Add UI-layer test `joint_dialog_applies_custom_name_or_falls_back_to_type_default`
to ensure the `JointForm` captures and submits names correctly.
* Format dialogs_assembly_tests.rs (trailing blank line)
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>
* claim: #8256
* Add initial Bulgarian translation (114 keys)
* Delete claim.txt
* Delete translate.py
* Delete assets/locales/bg.json
* Restore the bg catalog the locale list needs, fix indent
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Bulgarian: translate the strings the catalog tests require, drop ids that no longer exist
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>
* claim: #10963
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Copy, Cut and Paste: input and output schemas (#10963)
sketch.copy, sketch.cut and sketch.paste get typed contracts and reply
types in schema/sketch_clipboard.rs and leave unconverted.txt. `at` and
`translate` read as the paste reads them, with its message; the replies
are built from Copied and Pasted, with the same JSON as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch clipboard: import Vec2 from solidcore_record after the layer split
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
* claim: #10442
* Viewport: no dark patch between nearby bodies (#10442)
The ground shadow was one ellipse under the box around all shown bodies,
darkest in its middle, which for two bodies side by side is the empty
floor between them. Bodies whose footprints meet now share one ellipse
(as before) and bodies apart get one each, on the same ground level and
with the same rings; more than 64 groups fall back to the single ellipse.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #5431
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Create Sketch: pick an origin plane through a body in front of it (#5431)
While a dialog waits for a plane, the origin and construction planes are
drawn over the bodies, but picking sorted them by depth, so the face of a
body in front always took the click. When the nearest face hides the
nearest plane under the pointer, the planes now sort just ahead of that
face (in their own depth order, still behind a profile on it). A face at
the same depth as a plane still wins the tie (#31), and nothing changes
when no dialog is waiting for a plane.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* view_confirm_corner: draw the line where the view shows it
Create Sketch now picks the origin XY plane through the extruded box, so the
sketch is flipped to face the view and (50, 0) -> (70, 10) fell outside it.
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>
* claim: #5961
* Sketch: sketch.move_points moves several points, each to its own target (#5961)
A script editing a traced outline could move one sketch point per call,
and only by its id: an agent that had to move two points of a
490-segment polyline redrew the whole sketch instead, breaking every
dimension and feature built on it.
sketch.move_points {moves: [{point | near, to}], tolerance?} drags each
point to its own target in one edit (one undo step), as sketch.move_point
drags one, and the sketch is solved. `near` picks the sketch point
closest to a coordinate, refused when none lies within `tolerance`
(default 0.1 mm) or two are as close. Every point is found before any
moves. The reply gives each point's id, its target, where it ended up and
whether it got there, so a fixed or constrained point is reported rather
than silently left in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Move the new command label to a pending locale file
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Fix the new command for the sketch store and geometry split
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Carry the new command label in every complete catalog
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* sketch.move_points reads the sketch through sketch_store
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>
* Open a design from a newer SolidCraft with a warning instead of refusing it
A newer format number or an unknown change no longer blocks opening. What
this version can read opens; a feature it can't read stays on the timeline
with an error, and fields, sections and container parts it doesn't know are
named in the reply and written back by Save, under the file's own number.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Saving a newer design from the CLI, and editing a feature this version can't read
`run --save` writes a design holding a feature only a newer build can build;
`--out` still stops, since it would export the part without that feature.
Edit Feature on such a feature says why instead of opening the palette.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Translate the newer-design notice for the complete catalogs; clippy
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7273
* Viewport: drag a component as a GPU layer of its own
Each step of a Move triad drag placed the component anew, and the
viewport then rebuilt the model scene and uploaded it again. The dragged
bodies are now drawn from the scene built where the drag found them,
moved on the GPU by the drag's motion; the rest of the model stays as it
was. In the viewport a step meshes, builds and uploads no geometry;
only the transform (and the ground shadow) changes.
- gpu: Frame::moving draws a second scene with its own model matrix
(U.model, applied in every vertex entry, instanced parts included);
blocks, evaluated chunks and instanced triangles pass between the
model scene and the layer instead of being uploaded again; glass is
sorted in the layer's frame; the AO prepass and see-through pass
cover it; the frame key includes the layer.
- doc: ModelBody::display_moved_from gives the rigid motion between two
worlds' copies of one solid.
- ui-egui: the model scene without the dragged bodies is built once;
dragged bodies are matched by name; the step's body boxes are the
start boxes moved; selected or hovered dragged bodies light where they
stand. A drag back where it began keeps its layer. A section, an
analysis, continuity seams or no GPU draw the drag as before;
capturing the move makes the scene whole again.
* feat(ui): a setting to keep the highlight under the pointer while Move drags
A Move triad drag drawn as a GPU layer picks nothing under the pointer, which keeps a step from
meshing the dragged bodies where they stand and from looking up the Browser row a highlight
follows. Preferences > Tools > Assembly Move > "Highlight what is under the pointer while dragging
a component" (`move_drag_highlights`, off by default) picks and highlights as before.
Also: the drag layer test takes `as_chunks` (clippy 1.99's `chunks_exact_to_as_chunks`).
* fix(ui): say when the Move drag highlight setting applies
The label names Move's arrows, and the tooltip says that with a section or an analysis shown the drag highlights all the same, and that a part dragged along its joints never does.
* claim: tap marking records once in the model cache
* Model cache: write each tap marking record once and share it between states
Problem: every state of a saved model wrote the cosmetic tap markings of all the features
before it, so the records grew with the square of the features. A design of 400 marked
cylinders wrote 34 of its 37 MB manifest that way: past the 32 MiB limit the save thinned the
cache (590 of 806 states left out, opening evaluates them again) and wrote the manifest
several times while choosing what to keep.
Solution: the records keep a list of markings, each written once (keyed by its text, as the
sheet bodies are); a state names its markings as runs of places, like its bodies and
sketches. A marking at the same place in the state written just before, equal to the bit, is
found without writing it as text. A thinned model keeps the whole list and counts it once;
the reader counts the markings with what a state holds. The cache format is now /6: an older
cache is a miss and the design is evaluated as before. The design document is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Saved model: count each tap marking by its text against the held limit
A state clones every marking it lists, so a run repeating one large marking could hold far more than MAX_HELD items of memory.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Markus Kaltenbrunner <kaltenbrunnermarkus@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
A selected face was filled flat in `sel_face`. An imported mesh (STL, OBJ)
is a single face, so clicking it in the viewport painted the whole part as
one flat blue silhouette and hid every wall, pocket and edge. The fill is
now lit like the hover fill, which already was.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch: an outline drawn inside a shape past its neck is refused, not looped
An inward offset of a closed outline wider than half a neck of the
shape would pinch it into two; one chain can't be that, and its pieces,
each joined only to its neighbours, ran back over each other in a loop.
When the outline itself doesn't cross itself and its offset would, the
offset is refused like other offsets the shape can't take. An outline
that already crosses itself (a bow tie) keeps its crossing offset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Loop guard: the arc's direction comes with its source, not with the offset piece
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Loop guard: a touch counts, lines along each other count, tolerances from the outline's size
An outline split where it crosses itself only touches itself there and
was taken for simple, so its offset was refused: between pieces that
aren't neighbours a touch now counts as crossing. A neck exactly twice
the distance gives two lines lying along each other, which cross at no
point: such an overlap now counts too. The tolerances come from the
outline's own size plus the coordinates' rounding, not from how far it
lies from the origin.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch offset: pass the arc direction from the outline after the dog bone change
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>
* Sketch: a selection held back in part follows the pointer, not past it
Dragging a selection part of which cannot move (a rectangle with a corner
on the origin, all four sides selected) took the dragged side nearly twice
as far as the pointer. `sketch.drag` reported the grab where the average of
all dragged points went; the held corner kept that behind the side, and the
next frame's catch-up overshot. It now reports how the dragged point that
got furthest toward the pointer moved, so what can follow moves 1:1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch drag test: unwrap the world state after the engine change
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>
* claim: #7126
* Sheet metal: a corner torn between flanges of two features closes (#7126)
With the rule's corner relief `tear`, a flange made after another,
meeting it at a corner of the base, left the corner open: its setback
stopped the earlier bend at the corner while that panel ran on R + T
past it (an (R + T) x BA rectangle missing in the flat), and the new
panel stopped at the corner, R from the earlier panel's inner face
rather than the rule's gap. Folded, a hole about R + T across showed at
the base corner. A corner between the flanges of one feature is torn
(#809); one between two features was not.
Such a butt corner is now closed and torn: the new panel ends the gap
from the earlier panel's inner face, and both bends run on to where
their panels' ends start, each end on the plane through its start at
the corner (on the bend's outer face) and the outer edge of its panel's
end. In the flat that end is (cos(th - phi) - cos th)/(1 - cos th) of
the way along at the turn phi (sin phi at 90 degrees), one Bezier of
degree 9 (`tear_curved`). Folded, the corner closes but for a sliver.
In a thick sheet the bends' inner faces would cross beside the corner
(at 90 degrees where R^2 < (R + T)(R - g): with the gap 0.8 from about
2 mm). There the new bend's end eases toward (1 - cos phi)/(1 - cos th)
of the way along (`tear_ease`, still one Bezier), as little as keeps the
bends apart; the earlier bend and both panels' ends stay as they are.
A corner that can't be torn so (opposite or over-90-degree bends, a gap
wider than the radius, overlap in the flat) keeps what it had, with a
note.
The test that pinned a two-feature corner as never torn keeps its
open-corner half; the two-feature corner moves to the new tests, which
cover sheets from 0.9 to 3 mm.
Fixes#7126.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PsiqyXWBFGTCX9J51bhysc
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Dimension: aligned across the line, horizontal or vertical by the pull past an end
Review on #7355: the aligned length must stay the default, and the box
rule shrank its zone to a thin strip on a shallow line. Now `auto` is
the distance between the two points everywhere across from them
(between the perpendiculars through them, and a tenth of their distance
past), and only a clear pull past an end changes it: up or down from
that end gives the horizontal distance, sideways the vertical one. The
two meet on the diagonals through the end, so both zones stay wide on a
shallow or steep line. Midpoint pairs keep main's box rule.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Dimension: two midpoints keep the distance between them past their box's corners
As before #7355 and on main: text above or below the midpoints' bounding box
measures horizontally, beside it vertically, and anywhere else (inside it or
past its corners) the distance between them. The corners fell through to the
line rule and became horizontal or vertical (found in review).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Update egui/eframe to 0.37 (WIP)
egui, eframe and egui-wgpu 0.36 -> 0.37. SolidCraft's egui-wgpu patch is
rebased onto 0.37.0; the vendored accesskit_macos is dropped (0.27.2 has
the #7535 fix).
Not finished: 36 solidcraft-ui-egui lib tests fail, and the full lib run
crashes with SIGSEGV when tests run in parallel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: clear texture deltas that egui 0.37 refuses to drop
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Drop non-finite pointer input before egui; keep test time monotonic
egui 0.37 debug-asserts on NaN wheel deltas, on time moving backwards in the
pointer history, and on add_space inside a Grid.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Hold a click that follows typing in the same frame until the next frame
egui 0.37 drops a field's focus on a click elsewhere when the field first
reads its style, before it reads the frame's typing, so text typed in the
frame of a click was lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Name a document tab's close button "Close tab"
egui 0.37 names an unnamed widget after its tooltip, so the tab's × became a
second control named Close beside a dialog's. The space mouse guard test now
clicks where each button was: by name it found nothing behind the guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Turn on egui's default_fonts: 0.37 no longer does
The built-in Hack is the UI's symbol fallback (#2955). The app got it through
eframe, but solidcraft-ui-egui on its own (its tests) did not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Text menu: act on the selection the menu click took the focus from
egui 0.37 drops a field's focus on a click elsewhere before the field draws,
and an unfocused field loses its selection, so Copy, Cut and Paste from the
right-click menu saw no selection.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Placement field: keep its native height under egui 0.37's interact floor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: pin the theme's 2/3 point radii; measure Japanese ribbon labels with a Japanese face
egui 0.37 rounds widgets 4 points by default, which the theme deliberately
does not follow. The ribbon test measured Japanese with missing glyphs, whose
width changed in 0.37; with BIZ UDPGothic, 駆動 wraps after its particle like
変換 does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Tests: click the render inspector's Perspective checkbox by position
egui 0.37 orders the accessibility tree differently, so the name found the
stage's Perspective segment first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Translate "Close tab" for the complete catalogs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point the focus workarounds at emilk/egui#8763
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Cargo.lock: update only what egui 0.37 needs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Web: Id::new -> Id::unique for the wasm-only windows
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Command search: keep its hint centred under egui 0.37's interact floor
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* egui 0.37 renames in code main added since the branch
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* egui 0.37 renames in code main added since the last rebase
Id::new to Id::unique, ui.id() to ui.scope_id(), WidgetType to Role, the
wheel events' source, and the button stroke read from the widget visuals.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Review: disclosures report expanded, late screenshots drained, chat width reset on both sides
A disclosure's node says whether it is open as expanded, as egui's own
collapsing header does. The screenshot channel is drained every frame, so a
shot whose request was cancelled is dropped. Resetting the chat panel's width
forgets both sides' state, as before 0.37, and the request stays with its
window when tabs switch. Tests main added since the last rebase clear their
texture deltas.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* i18n: register the Catalan catalog
Add `ca` to the locale list, between Bosnian and Czech, and attribute
assets/locales/ca.json.
* Catalan (ca): translate preferences and the 61 new messages
- preferences: every prefs.* message is now Catalan (355 strings).
- the 61 messages added upstream since the last catch-up: the layout page,
sketch options, navigation options and the custom navbar anchors.
- drop the four keys upstream no longer has: thread.direction,
thread.left_hand, thread.right_hand and ui.print_face_one_body.
Work in progress: ui.*, cmd.*, render.*, the loose sentences and help.*
still mix Spanish and Catalan.
* Catalan (ca): translate the first half of ui.*
400 messages: the 3D mouse settings and calibration, the agent chat and the
AI preferences.
* Catalan (ca): translate ui.* up to the equation and edit-in-place messages
200 more messages: the toolbar and browser controls, colour vision, design
properties, the details panels and editing a linked part in place.
* Catalan (ca): translate ui.* up to the navigation bridge
200 more messages: the ribbon headings, the history panel, the graphics
presets, the physical material review and the mesh formats.
* Catalan (ca): finish ui.*, 1,284 messages
The ribbon headings, the properties, the slicer and point cloud panels, the
view angles and the workspace descriptions are now Catalan. ui.* has no
Spanish left.
* Catalan (ca): translate the command names, 508 messages
Every cmd.* entry, including the render library, the sheet metal tools, the
sketch tools and the named views.
* Catalan (ca): translate the CPU render settings and the environment panel
200 more messages: the CPU light rigs, the reflection targets, the resource
profiles, the preflight details and the stage and HDR environments.
* Catalan (ca): translate the render errors, the shot framing and the inspector
200 more messages: every render error, the frame presets and guides, the image
status, the HDR source details and the appearance inspector.
* Catalan (ca): translate the render library, the lights and the looks
200 more messages: the resource catalog, the physical light editor, the colour
looks and the GPU material and glass overrides.
* Catalan (ca): translate the render output, results and ribbon
200 more messages: the output editor and its size limits, the presentation
filters, the completed-image results, the aging and physical references and
the render ribbon.
* Catalan (ca): finish render.*, 2,392 messages
The saved shots, the stage panels, the studio lights, the subsurface and volume
transport, the wear layers and the render browser. render.* has no Spanish left.
* Catalan (ca): translate the standalone labels, 200 messages
The browser and panel labels, the sheet metal bends and reliefs, the boss, rib,
rest and snap fit drag hints and the issue report steps.
* Catalan (ca): translate the export, folder and material labels, 200 more
The export menu, the sheet metal bend table and reliefs, the font and history
options, the joint labels and the issue report help.
* Catalan (ca): translate the remaining panel labels, 200 more
The report and recovery messages, the profile and edge pickers, the rules
editor, the screenshot steps and the sketch selection prompts.
* Catalan (ca): translate the sheet metal, AI preferences and browser strings
200 more: the standing-up offer for Y-up files, the trim and stitch tools, the
AI availability and permission settings and the browser node names.
* Catalan (ca): translate the crash reports and a large part of help.*
200 more: the copies, the crash reporter, the DXF export progress, the file
loading statuses and the help texts for the sketch and solid tools.
* Catalan (ca): translate the home page, the hole presets and the material editor
200 more: the start page and its dates, the install steps, the issue report
notices, the assembly move handles, the material property card and the motion
keys.
* Catalan (ca): translate the navigation bar and the input presets
200 more: the navigation modes, the navbar customiser and its descriptions,
the network share warning, the fastener dimensions and the picking prompts.
* Catalan (ca): finish the catalog, 5,591 messages
The search box, the settings notes, the sheet metal personal rules, the sketch
report and text tools, the timeline menus, the updater and the web file
prompts. The catalog now has no Spanish left.
* Catalan (ca): translate the 634 messages the pending script had missed
The script only flagged strings identical to the Spanish or English catalog, so
every untouched string that differed from es.json stayed in Spanish: the whole
help.* reference, the import report, the joint limits, most preferences and the
fillet and continuity tips. They are now Catalan, and the script counts a
message as done only when a block file contains it.
* Catalan (ca): keep the sketch hints and stop splitting l·l
The sketch tool hints and engine errors are not in en.json, so they live in a
pending file: add the Catalan messages there instead of letting them fall back
to English. The new asset gets its attribution row under attribution/pending,
so this branch no longer edits ATTRIBUTION.md directly.
The painted-text sweep split labels on the bare middle dot, which is Catalan's
l·l inside a word: "Cancel·la" became "Cancel" and the test reported English
where the catalog is translated. Split only on the spaced middle dot.
* i18n: carry the complete catalogs in the pending sketch hints
A pending file that adds English messages also adds them for the catalogs
delivered complete (ar, he, ja, vi, zh), or i18n --check fails. They keep the
English text, as in the Spanish catch-up branch.
* i18n: attribute the pending hints and fix the break message
The pending sketch hints add an asset, so they get the same attribution row as
in the Spanish catch-up branch (same file, so the two merges without a
conflict). The engine has nothing to break, so say so in English.
* Catalan (ca): translate the 30 new messages
The blur preferences, the dimension step settings and the fillet turn modes
arrived upstream after this branch was opened; merge main and translate them.
* i18n: use US spelling in the pending hints and add en-GB
The English catalog is US-spelled, so the English fallbacks say center; en-GB
keeps centre for the four messages that differ.
* Catalan (ca): fold the pending hints and catch up with main
`main` folded `sketch-tool-hints.json` into every catalog when #10895 merged
and deleted the pending file. This branch still carried it, so the merged tree
failed `cargo xtask lists` with 186 duplicate ids and one repeated attribution
row. The 26 hints now live in `ca.json` and the two pending files go; the
catalog row in `attribution/pending/catalan-catalog.md` stays.
The 118 messages `en.json` gained since this branch opened are translated: the
26 hints and 92 new ones (AI HTTP and instructions preferences, surface-map
import, the ribbon-tabs-only view, hidden-edge selection, recovery, the custom
view navigator and a handful of dialog tips). The obsolete
`cmd.inspect_colored_components_disable` is gone, so `ca.json` matches
`en.json` key for key.
The shaded-with-hidden-edges label said `Sombreado`, which is Spanish.
---------
Co-authored-by: CaRLymx <24634598+carlymx@users.noreply.github.com>
* Point clouds: a PLY file's edges and faces as beads and triangles (#5151)
A PLY file's `edge` and `face` elements become segments and triangles
between the cloud's points (a face as a fan), each with its own properties;
a file with them is read whole, since thinning would cut them. A point's
numbers vary along the segments and across the triangles that meet there,
an element's are constant on it.
Segments with `width` and `height` are drawn as beads of that stadium
cross-section, meeting mitred where one runs into the next, from one
indexed template; without them, as lines. Points no segment or triangle
ends on stay dots. A range filter cuts an element where its value crosses
an end, on the GPU and in the tracks alike: each element weighs its
measure (a dot 1, a segment its length, a triangle its area, each kind
over its own total) and spreads it exactly over the values it covers, a
segment's evenly and a triangle's as the tent its corners make.
Columns saved without the element they are on are on the points, so
designs saved with point properties alone still open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: past max_points a PLY is thinned again; the near plane cuts elements exactly
A PLY with edges or faces and more vertices than `max_points` was refused, where it used to be
thinned. It is thinned again: thinning would cut its edges and faces, so they are left out, with
a warning, and raising `max_points` keeps them.
A segment or triangle crossing the near plane moved its corners behind the eye to a made-up
place, which pulled a wedge across the view. A triangle now keeps its true clip-space corners,
which the rasteriser clips exactly. A bead is cut where its axis crosses the plane, a crossing
exact because clip-space coordinates are affine along a line, and its numbers are exact there.
A ramp accepts MAX_BINS bands but an element was split into 4097, so a ramp finer than that lost
measure: half of a segment at 8192. Only the whole numbers have more bands than MAX_BINS.
`xtask mesh-decisions` reads any field named `triangles` as a mesh; a cloud's own complex holds
none, so its three modules are skipped with that reason.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a ramp's measure is whole past both its ends; a column on the points says nothing
A ramp of MAX_BINS bands has one band below it and one above it, so an element running past both
ends crosses MAX_BINS + 1 bands past its first. The limit was one short, and such an element lost
what lay above the ramp.
A column on the points now leaves `on` out when saved, so a design holding only points keeps the
form it had, as the field's comment and the pending format note already say.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: a set filter keeps part of a bead whose ends differ; an unreadable point takes only what ends on it
The set filter's whole-element test read the first corner alone, so a bead whose
first end was not kept vanished, while the range tracks counted the part kept. It
now drops a bead only when one value at every corner is not kept; otherwise the
fragments decide, as for triangles.
A PLY with edges or faces and one unreadable vertex was refused whole. It now
keeps the subcomplex the readable points span, renumbered, with its numbers, and
warns about what it left out. A file with numbers on its points gains the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Point clouds: build the triangle corner weights without a dynamic vector write
The Windows D3D12 shader compiler rejected w[vi] = 1.0 (error X3500).
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>
* Dialogs: remember values and options for this app session (off by default)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Dialogs: translate the remember-values help text again
The help text changed to say the setting is off by default, and the
complete catalogs (ar, he, ja, vi, zh) lost their translation of it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: recall gives a new face a fresh thread suggestion and rebuilds Move/Hole mode inputs
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Dialogs: format recall
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* Status line: say which file an Open or Insert brought in, and whether it got a new tab
"Open done" and "Insert done" were English text built in code, so they stayed English in every
language and didn't say what was opened. They now read "Opened <file> in a new tab", "Opened
<file>" when a blank design was replaced, or "Inserted <file>", from the message catalogs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Status line: an opened design is named as its tab names it
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Status line: an Open's test and Korean strings
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Status line: an Open's message in the chosen language, and none for a design that found no tab
The message is built outside the window's language scope, so it takes the locale from the preference as the other statuses of the file loader do
An opened design is named as its tab shows it (a default name translated), an inserted one as its component is named
An open that finds no tab left keeps the error and answers it, instead of naming the design shown as opened
The test injects the finished open instead of running and polling a worker
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Status line: a design to recover that finds no tab stays offered
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Status line: a design to recover that hits the tab limit stays offered too
An Open or recovery with every tab taken was refused before the design
was read, and a design to recover was dropped from the list on that path.
It goes back in the list, as when its read fails. The staged-open test
reads the new status.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A bend that cut-outs across it split into several cylinder faces came out slit, silently: only
the picked piece was bent, the rest of the bend was cut away with the cut-out, and the volume came
out short. Picking the other pieces too was refused ("two faces of the sheet that meet along more
than one edge"). A picked bend cylinder now takes every coaxial piece of the same radius between
the same two faces, on both skins; in the flat, a face that meets its bend neighbour along several
collinear runs gets one bend edge, and the notches between them and the bend zone's pieces there
become holes of the sheet.
Test: a_bend_split_by_slots_across_it_converts_as_one_bend (slots cut through a tray's unfolded
bends, one piece picked or every piece): the volume, flat size, outline area, bends and the three
cut-outs are the source's.
Fixes#7530.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #11048
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Intersection Curve: take two sketch curves, no helper surfaces (#11048)
Intersection Curve now also takes curves of other sketches. Each curve
stands in for the surface you would get by extruding it along its own
sketch's normal, both ways and as far as needed, so two sketch curves
(or a curve and a face) give their 3D crossing without the two Surface >
Extrude features it used to take. The surface is never added to the
model. Sketches on parallel planes are refused with an error saying why.
The result stays a linked 3D wire that follows edits to either curve.
In the tool, a curve of another sketch fills a side like a face does;
after the first pick the status bar asks for the second, a right-click
starts the picks over, and the hint returns once the curve is made.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Intersection Curve: keep other tools' right-click, update the tooltip, add a UI scenario (#11048)
The right-click change now applies to Intersection Curve only: Project to
Surface and Spun Profile behave as on main again.
The toolbar tooltip now says Intersection Curve also takes curves of other
sketches, in every catalog that has it, using each language's wording from
the Project to Surface tooltip.
New UI scenario intersection_curve_from_two_sketch_curves: a line on XY and
a line on YZ give one linked 3D wire along X at y = 0, z = 5; it also checks
the second-pick prompt and that a right-click starts the picks over.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GG4jU9aCv381PgLCsseQMP
* Intersection Curve: a picked spline wall meets whole, in one curve (#11048, #9009)
On the SMLib kernel a fit-point spline's wall is one face per span, and
Mesh::surface_patch keeps smooth-joined faces apart, so a face pick met
only the span under the pick and gave part of the crossing (#9009 again,
for faces as well as for sketch curves). A face pick now takes the faces
joined across seams and smooth joins, never across a crease.
The imprint also splits a crossing at every face boundary of either
side, so a crossing came as one wire per span. The pieces are now joined
end to end into one wire, keeping their exact curves; a piece that runs
the other way is reversed as a record curve.
A curve side's sweep is sized from the other body's exact bounds, not
its display mesh. The #9009 test's premise that the spans are one
surface_patch no longer holds and is removed; its checks are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Intersection Curve: a picked face joins only tangent spline faces, from exact geometry (#11048)
Which faces a face pick means is now decided on the exact surfaces, not the
display mesh: the picked face and the spline faces joined to it at least
tangentially (the kernel's seam check), so a spline wall's spans still meet
as one (#9009) while a plane, a round or any analytic face is that face
alone. The pieces of a crossing are joined within ten times the kernel's
intersection tolerance instead of a fixed 1e-4 mm.
Tests: a flat face beside a round and the round itself each give their own
crossing; two sketch curves give one curve at 1/100 and 100 times the size.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Known-failing tests: the picked spline wall's intersection curve passes now (#11048, #9009)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Book: sketching step by step, with three clips and a numbered screenshot
The sketching page now opens with a first sketch in four steps: start a sketch on the Top
plane, draw a 60 x 40 rectangle and a 12 mm circle with typed values, dimension the circle
until the sketch is fully constrained, and finish. Each step has a short clip, and the last a
screenshot numbering the Sketch tools, a constraint, a dimension, the sketch's state and
Finish Sketch.
doc_shots.py gains what these needed: a setup that runs before recording, so a clip starts
where the last one ended; sketch framing; Enter sent as two input frames; and clips that keep
their pauses (hovering a plane).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: the sketching page's general notes get a heading after Step by step
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: sketching adds Offset and Trim; the sketching figures again on 0.29.0
Two more clips after the sketching walkthrough: Offset draws the rectangle's outline 4 mm inside
it with one click (Chain), and Trim cuts a notch where a circle crosses the top side. All the
sketching figures are made again on SolidCraft 0.29.0, with the clips keeping a moment on each
result.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: solid features step by step, with four clips
The solid features page now opens with the sketch from the sketching page made into an open
box in four steps, each with a short clip: extrude it 12 mm, shell it with the top open,
round its outer top edges, then make it 20 mm tall from the timeline while the shell and the
fillet rebuild. The panel table and what follows get a heading of their own.
doc_shots.py: the feature shots share their steps with the setups that lead up to the next
shot, and fail when a feature on the timeline carries a warning or an error, so no figure
shows one. Shell comes before Fillet for that reason: shelling through the rounds needed the
kernel's boolean retry. trim_idle.py takes --hold, the seconds of each idle stretch to keep
(default 0.1 as before); the doc clips keep 0.8 s, so a reader sees each result. The app gets
up to three minutes to answer on a busy machine.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: features add Hole and Circular Pattern; the Parameters dialog, numbered
The features walkthrough gains two steps before the timeline edit: a 4 mm hole on the box's
floor, then Circular Pattern repeating it four times around the tube. The edit at the end now
rebuilds all of it. The parameters page gets a screenshot of the Parameters dialog numbering
apply as you go, user parameters, model parameters by feature, an expression and its value.
The features clips are made again on SolidCraft 0.29.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: components and joints step by step, with three clips
The components and joints page now opens with a two-leaf hinge in three steps, each with a
short clip: the two bodies made into components from the Bodies folder's menu (the first one
grounded), a revolute joint on their barrel ends with Flip turning Leaf B the right way round,
and a drag that swings Leaf B about the hinge while grounded Leaf A stays put. What follows
gets a heading of its own.
doc_shots.py builds the leaves from commands, with Leaf B lifted and set back so the joint
visibly brings it into place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: assemblies add Drive Joints; the assembly clips again on 0.29.0
A fourth step drives the hinge with Drive Joints: the slider swings Leaf B while a ghost shows
where it started, and OK keeps the new angle. The assembly clips are made again on SolidCraft
0.29.0.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Book: getting started, sheet metal and import/export get pictures
Getting started gains a section on the start page: a numbered screenshot (new design, open,
import, recent, samples, the File menu) and a clip opening a sample from it. Sheet metal opens
with a tray in three steps, each with a clip: a base flange from a sketched rectangle, edge
flanges from two top edges, and the flat pattern with its bends. Import and export opens with
the File menu, numbered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #10940
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Emboss: an emboss or deboss that changes nothing says so (#10940)
solid.emboss on a body its tool missed (a sketch on the far side of the
material, a depth that stops short) was recorded as a success with no
warning, so a sheet metal part looked embossed when it was not. The
evaluation now compares the bodies before and after and adds a warning
when none changed. A working emboss or deboss is silent.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
* Emboss: the changed-nothing warning names what to check, not a cause it can't know (#10940)
CodeRabbit pointed out that an emboss can change nothing for other reasons
than missing the body (for example a profile that lies inside material that
is already there), so the warning no longer claims the profile misses the
body. It says the operation changed nothing and suggests checking where the
profile lies, which side of the face the sketch is on, and the depth.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Emboss test: put the missing sketch below the sheet, since main's sheet metal emboss now cuts from its underside (#10940)
On current main a deboss from the XY plane reaches the sheet (it is changed and gets
the sheet metal flat-pattern warning), so the test no longer showed an emboss that
changes nothing. A sketch 5 mm below the sheet misses it on both bases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
* claim: #11271
* Polygon By side: Length and Sides boxes, the typed length a side dimension (#11271)
The edge polygon tool showed no value boxes after its first click. It now
shows Length (the side from the first click) and Sides, as the inscribed
and circumscribed modes show Radius and Sides. A typed length places the
second corner and becomes a Length dimension on the first side:
sketch.draw.dimensioned takes a `length` role for sketch.polygon.edge,
an alternative to its radius (as_drawn still adds the radius when no
length is given; both together are refused).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Polygon By side test: read the design's own state so it compiles against main (#11271)
Main's Session::world_state() now returns a Result, so the new test failed
Quick compile on the merge. The sketch is in no component, so the design's
own state shows the same sketch on both the old and new bases.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Polygon By side test: take Vec2 from solidcore_record, where main moved it (#11271)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: thocsmith <193096300+thocsmith@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The fillet keeping of #5677 swaps an arc's ends when its sweep jumps by more
than half a turn with both its lines turned. Judged against a reference that
shows the corner mirrored (positions before a move, or a mirrored start), a
fillet the solve left exactly where it was looks turned, and the swap made it
its long complement: a 45-degree fillet on a 135-degree corner became a
315-degree arc with both lines cutting back through it, while the solve
reported ok.
The swap is now decided by the lines: an arc whose tangent lines all leave it
after the solve already rounds its corner and keeps its ends. A swap still
gives a corner its arc back when its lines cut back through it. A second test
keeps a real fillet over three quarters of a turn whose corner mirrored as the
long arc its lines leave (a narrower fix, letting a swap only shorten an arc,
would break it).
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
A lip kept its rim face as a plain point, so it got no persistent face
name. When a parameter made the box taller, the point stayed below the new
rim and the lip failed ("the plastic feature's placement is no longer on
its body face"). The face is now a face pick, as a boss's is, and the lip
names its body like the other placed plastic features. Both are saved as
the same [x, y, z], so files don't change.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Ribbon: an option to fold it to its tab row (#10112)
Preferences > Layout > "Show only the ribbon's tabs" (off by default), the
Ribbon Tabs Only command (view.ribbon_tabs_only), and its settings-profile key.
Rebuilt on main's redesigned Layout page.
* Ribbon Tabs Only: switching tabs with a panel menu open no longer keeps the overlay open (#10112)
A tab click now forgets the open panel drop-down of the previous tab, so the pointer leaving or Escape still closes the overlay.
* Ribbon Tabs Only: count its settings-profile key in the profile membership test (#10112)
* MCP: compact inspect_design and tool JSON, off by default (--compact / SOLIDCRAFT_MCP_COMPACT)
Split out of #2901 per review: the smaller inspect_design changed the default
for everyone, so it is now an MCP server option that is off unless asked for.
- solidcraft-cli mcp --compact, or SOLIDCRAFT_MCP_COMPACT=1 (true/yes/on;
0/false/no/off/empty leave it off; anything else is an error at start-up).
solidcraft_mcp::Options::resolve is the one place that decides, so an app
Preferences setting can feed it later.
- With it on, inspect_design summarises sketches (id, name, plane, counts of
points, curves, constraints and profiles, dof, status), returns full contents
only for the sketches named in `sketches` (names/ids, true, false or
"summary"), takes a `bodies` filter and measures only those; unknown names
list the existing ones. Tool JSON is compact rather than pretty-printed.
- Off, tools/list, argument checks, inspect_design and tool JSON are exactly
as before.
- document.inspect gains an opt-in `sketch_counts` (plane, points,
constraints); without it its reply is unchanged.
Tests: both modes in the MCP suite, Options parsing, and the CLI flag and
variable end to end.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MCP compact output: a Preferences setting too (AI › Agents through MCP)
The desktop app's Preferences › AI gets "Compact output" under a new "Agents
through MCP" section, off by default and shown with the chat off too. The app
reports it on the control channel as `mcp.options` ({"compact": bool}); the
agent chat lets its own MCP server read it.
A bridged MCP server (`solidcraft-cli mcp --connect`) asks the app when an
agent first lists or calls a tool and keeps the answer for the session, so the
tools it listed keep their shape. An app not running yet, or busy past 2 s, is
asked again on the next call; an app from before the setting counts as off.
`--compact` and SOLIDCRAFT_MCP_COMPACT=1 still turn it on whatever the app says.
Tests: the server follows the app's answer (waits for it, keeps it, the flag
still wins); the checkbox reaches `mcp.options` and Restore Defaults clears it;
the chat gate allows `mcp.options`. docs/mcp.md and docs/control-protocol.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MCP compact output: review fixes (mcp.options never blocks, headless test, locales, prefs doc)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MCP compact output: the setting is a section of the AI page; the bridged test answers mcp.options
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MCP compact output: the preference test sets it through ui.prefs; rustfmt
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* MCP compact output: look for a newly started app before asking its options; counts from sketch.inspect for an older app
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4332
* Two-distance and distance-angle chamfers of free-form rims: a test on the SMLib kernel (#4332)
A spline, Bézier or text outline's rim used to be refused by the two-distance and
distance-angle chamfers. The SMLib kernel (#11351) chamfers it along the rim; this test pins
that down on a four-Bézier oval, picked as its tangent chain the way a pick takes it: the
volume taken off is the analytic one for two distances (both ways round) and for a distance
and an angle.
* Sketch: the geometry of a pierce, a point where another sketch's curve crosses this plane (#3599)
* Sketch: the Pierce constraint, a point on a curve of another sketch (#3599)
* Engine: draw a Pierce constraint at the point it holds (#3599)
* Engine: the pierce tests wait for the command that is not written yet
* style: rustfmt, the imports of the pierce geometry in the order rustfmt wants
The rebase onto the current main resolved the rename of Plane, Vec2 and Vec3
from solidcraft_geom to solidcore_record and left the two use lines in the
order the resolution wrote them. rustfmt wants them the other way round, and
the fmt phase of the gate reads the whole tree.
No token of the file changed: the same 2983 tokens before and after, only the
order of the two use lines moved.
---------
Co-authored-by: bherbruck <bherbruck@gmail.com>
Co-authored-by: vdirienzo <vdirienzo@users.noreply.github.com>
* claim: #10745
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Control channel: ui.menu refuses missing targets, reports the History item menu, and tells the joint menu items apart (#10745)
- ui.menu looks a target up and refuses one the design doesn't have ("no body `NoSuchBody`").
- The History bar's feature menu is the shared context menu (Target::Timeline), so ui.menu
reports it, ui.menuPick runs its items and {"type":"timeline","feature":id} opens it.
Its Show All toggle moves to TreeState so the menu can list Delete after the marker.
- Edit Joint Limits is ui.editJointLimits and opens Edit Joint with Limits expanded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Icon audit: open the section and canvas menus on a real section and canvas; a hidden section has a menu too
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui.menu: check a group's or folder's scope and folder name; Delete after the marker gets its own id
A group or folder target is refused unless its scope is a row of its component (`c<id>` or a
placement of it) and the folder is bodies, sketches or construction. The History item menu's
delete of everything after the marker is ui.deleteAfterMarker, so ui.menuPick can tell it from
Delete (review on #10746).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui.menu: a component's own c<id> scope is the design root's only; test that Delete after the marker deletes
The browser draws a part's folders and groups under its placements (o:<path>), so a c<id> scope
for any component but the root names a row that isn't there (review on #10746). The History test
now picks ui.deleteAfterMarker and checks the features are gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui.menu: a placement scope must be spelt as the browser keys it
o:012 parses to placement 12 but isn't the row's key (o:12), so Show/Hide would store a key no
row reads (review on #10746).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* History item menu: no Delete after the marker when nothing shown is after it
A timeline target for a feature the bar's component filter leaves out listed no features, so the
item did nothing (review on #10746).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui.menu: a plane target must be construction geometry
The plane menu's Show/Hide keys the feature's name as an origin item, so any other feature got a
menu whose actions don't fit it (review on #10746).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* ui.menu: accept the view navigator's and selection filter's menus, which main added
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* AGENTS.md and the contributing skill: say how to work as an agent
Two rules that the loop's own agents follow but the repository never
wrote down, so every agent that reads it had to rediscover them:
* The code, the comments, the commit messages and the docs are in English,
always. A comment in another language is a defect: the people who
maintain the code cannot review it. A user-visible string is not inline
in the code either; it goes to the message catalogs, and the UI text
skill says how to name and translate it.
* Parallel work runs in parallel, and dependent work stays sequential.
One subagent per independent part, with a self-contained brief, and no
two subagents writing the same file. A fix and the test that proves it,
a refactor and the callers it breaks: those stay in order.
AGENTS.md is the router, so it gets the two rules in one line each, and
skills/solidcraft-contributing/SKILL.md gets the "Working as an agent"
section with the detail.
* AGENTS.md: fit the two agent rules under the router cap
`cargo xtask skills` reads the router as a router: one line per rule,
with the detail in the skill the line points at. The two new lines
broke that contract twice over. They carried their own detail (a
comment in another language is a defect; one subagent per part, no two
writing the same file) instead of pointing at the "Working as an
agent" section that already holds it, and the file grew to 5361
characters, over the 5000 a router may have.
So the two lines shrink to what a router line is, and five clauses
that repeated a rule the pointed-at skill already states in full give
up their place:
* "Act when the code, docs and rules settle a question" is in the
contributing skill, in more detail.
* The unsafe allowlist is the crash-safety skill's own list.
* "No second PR for an issue a lower-numbered one closes" and "never
force-push main, no pushes just to catch up with main" are the
contributing skill's claim and push rules.
* "Do not switch branches while the gate runs" is in the contributing
skill too.
No rule is lost: each one still has exactly one place that states it,
and that place is a skill. The router is 4970 characters.
Open GitHub issue form filled the issue through the link only up to
2,000 characters, with every space and line break encoded as three, so
most reports opened GitHub with the title alone and their text on the
clipboard, and many arrived empty (#10556, #10654).
The link now encodes only what a query value can't carry (a space is
`+`) and is limited by what GitHub keeps: 6,000 characters, and 7,000
once its sign-in page encodes it again. A report too long for that still
goes through the clipboard, whole.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #7641
* Sketch on a thickened surface follows the face (#7641)
Thicken dropped the kernel's face history, so Create Sketch refused
every face of the solid. Carry that history through, the same way
Shell and Trim do.
The regression covers the offset face and an end wall, and checks
that a boss on the offset face follows a thickness change and reopens.
* claim: #9623
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* Sheet metal and plastic rules: Thickness in a rule's expressions is the rule's thickness
A rule's expressions read `Thickness` as the rule's thickness only while the
design had no parameter of that name; with one, the default gap, reliefs and
a `Thickness` bend radius took the parameter instead (a 2 mm rule beside
Thickness = 10 mm closed corners to a 10 mm gap). Now `Thickness` is always
the rule's, as documented; the rule's own thickness still reads the design's
parameters, so `thickness: Thickness` keeps naming the design parameter.
Renaming a parameter called Thickness, its users and what it drives follow
the same reading: only a rule's thickness field refers to it. Renaming a
parameter to Thickness is refused while another rule field uses it, since
that field would then read the rule's thickness instead.
Fixes#9623
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
---------
Signed-off-by: Maurizio Monticelli <maurizio@whatwedo.ch>
* Claim #9736
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assembly: a move not captured yet is an unsaved change (#9736)
A component moved without capturing its position left the design reading
as saved: file.save wrote the file without the move and said nothing, and
closing the tab didn't ask. Now:
- Session::is_dirty is true while a move is pending, so closing a tab or
quitting asks "save changes?".
- file.save and file.save_as refuse while a move is pending, naming the
component and the capture and revert commands.
- In the app, Save As (and Save As JSON, Save a copy, Download design)
asks the Capture Position question first, as Save already did; a tab
closing after "Save" waits for that answer, and Cancel keeps it open.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Close: the save-changes card waits behind the Capture Position question (#9736)
Saving a tab that closes asks the Capture Position question first; the
"save changes?" card stayed drawn over it and took a second answer. It
now hides and ignores answers until the question is answered. The design
container doc says a save stops for a move not captured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assembly: an exploded view shown is not a move to save (#9736)
explode.show displays a view through the pending moves, as an uncaptured
move does. Those placements last only while the view is shown, so they
don't make the design unsaved and don't stop a save
(Session::moved_uncaptured). A move on top of a shown view still does.
The relink test saved with a move pending; it captures it first now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Assembly: the pending-move save test reads the world state as main now gives it
world_state() returns a Result since the kernel merge; the test unwraps it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Docs: add BoxInsertDesigner to the showcase
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Docs: attribution rows for the BoxInsertDesigner showcase recording
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The joint origin input only accepted faces, edges and vertices, so the world origin and sketch points could not be picked to place a joint. Extend the origin input's snap mask with ORIGIN and POINTS and resolve a sketch point (and the empty-body world-origin vertex) to a world snap in snap_of, so a joint can be placed on the design origin or a hole centre like Fusion.
* Sketch Project: a face or edge of a part's second placement projects where that placement is (#9957)
Projecting the face of `Block (Part:2)` failed with "face of Block: the
face no longer exists". The pick's body name was turned into the
definition's own name, and the model a sketch reads holds only each
component's first placement, so the face was looked for on the first
placement, somewhere else.
Sketch picks now keep the name the browser shows, and the further
placements a sketch's links name are placed into the model it reads
(`with_placed_copies`), when the sketch is evaluated and when a link is
added or refreshed. Only the named ones are placed. A link to a body
the model lacks also makes the sketch follow every placement, so moving
the copy moves what was projected from it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Sketch Project: a projection from a second placement follows renames of its part and body (#9957)
The link keeps the copy by the name the browser shows (`Block (Part:2)`),
which a rename of the component or of the body changes. component.rename
and body.rename now rename it in the sketches' links as well.
The copies a sketch reads are found through their component's bodies,
not by going through every body for every placement.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The Scale dialog gets a Type row, Uniform or Per axis. Per axis shows
X, Y and Z rows and sends factors, which solid.scale already takes. A
Scale made with factors opens per axis with its three values; before,
they rode along as an extra and the dialog's factor did nothing.
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* claim: #4644
* Add the Fair Surface and Match Surface dialogs (#4644)
Neither command had a dialog, so the toolbar and command search ran them
with no parameters and they failed with "`body` is required".
Fair Surface picks the surface or some of its faces and sets the
strength, the iterations, whether the edges are kept and an optional
tolerance (blank: 0.001 of the body's size). Match Surface picks the
surface or face to rebuild, the target and an edge chain of the target,
and the continuity (G1, G2 or G3, default G2). Both preview, and Edit
Feature restores the saved picks and settings. New labels go through
the catalogs with English, Japanese, Chinese, German and Korean text.
* Keep surface-dialog strings pending with complete translations (#4644)
* Fair tolerance is a length; Fair and Match picks reopen on their saved bodies
The Fair dialog's tolerance now goes through the same design-unit length
conversion as the other surface lengths, on OK and when an edit is refilled.
Editing a Fair or Match feature looks its face and edge picks up on the body
they were saved on, so a coincident face or edge of another body cannot take
their place.
* Surface dialogs: strings stay in the pending catalog only (#4644)
* Surface dialogs: edge picks use the session world, as main's edge_sel now does (#4644)
* Surface dialogs: refuse to refill when a saved pick no longer resolves, and pass body names by reference (#4644)
* Surface dialogs: Fair and Match now open dialogs, so the refused-command tab test no longer lists them (#4644)
* Stderr: a closed stderr no longer stops the app, the CLI or the MCP server (#10111)
`eprintln!` panics with "failed printing to stderr" when stderr is a
pipe its reader has closed. A process another program starts writes
into such a pipe: `solidcraft mcp` under an agent, and the app that
one starts, which inherits it. #10111 is the app stopping that way
while an agent (Codex) connected. `solidcraft_geom::note!` (re-exported
as `solidcraft_engine::note!`) writes the same line and ignores a
failed write, as the CLI's logger already does; the app, the CLI, the
MCP server, the crash hook and the crates they run use it instead of
`eprintln!` outside tests.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stderr: test that a closed stderr leaves the CLI's exit code as it is (#10111)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Stderr: the old name's alias and the fuzz command don't stop on a closed stderr either (#10111)
`solvecraft-cli` writes its deprecation notice before it starts
`solidcraft-cli`, so an agent still configured with the old name got the
panic before the MCP server started (found in review). It and the fuzz
command now write through `note!` too, and the test covers the alias.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: the component menu keeps its everyday actions and groups the rest in flyouts
A component's or placement's right-click menu had grown to about thirty entries in seven
blocks. Activate, Edit in Place, Open Linked Design, Move/Copy, Show/Hide, Isolate, the
clipboard, Delete, Rename, Appearance, Physical Material and Properties stay on the menu;
Ground, Capture and Revert Position, Unlock Contents and Turn Up Axis move to a Position
flyout, Relink Source or Extract to File and the exports to File, New Component, New Group
and Paste New to New, and Show All Bodies, Cycle Component Color, Expand All and Collapse
All to Display. context_menu::entries lists a menu with its flyouts, as ui.menuPick reads it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: the component menu nests its flyouts by topic, and flyouts no longer scroll
Position (Ground, Capture and Revert Position, Unlock Contents, Turn Up Axis), Component
(New Component, Rename, Appearance, Physical Material, Cycle Component Color, Relink Source
or Extract to File, Properties; Design on the design's row), Export (Export, Save As Mesh,
Send to Slicer) and Browser (New Group, Expand All, Collapse All) each open a flyout; Paste
New stays with the clipboard and Show All Bodies with Show/Hide. Flyouts open inside flyouts,
so Appearance keeps its quick choices inside Component; the keys walk in and out of them.
A flyout no longer scrolls or sits away from its row: every flyout has its own area, measured
whole, where a short one used to squeeze every later one into its height. A component's only
body is no longer offered as a component of its own on a folded Bodies folder's entries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: the component menu starts with what a double-click does, and keeps the old order of its sections
A linked component's menu starts with Edit in Place and Open Linked Design when a double-click
edits it in place (double_click_components), and with Activate otherwise. Position, Component
and Export follow Move/Copy, then the clipboard, then Show/Hide and Isolate, with Browser last,
as the sections came before the flyouts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: Show All Bodies is on a component's menu only while a body is hidden
It is listed while a body is hidden itself, in a hidden placement, or in a hidden Bodies folder or
group of bodies, anywhere in the design, since that is what it shows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: Relink Source and Extract to File sit right below Rename in the Component flyout
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: Placement and Organize flyouts, Turn Up Axis back as a flyout, and flyout fixes
Placement (Ground, Turn Up Axis, Capture and Revert Position, Unlock Contents) follows
Move/Copy above the first separator, Turn Up Axis opens its two turns in a flyout again, and
the Browser flyout is called Organize. The Component flyout has three blocks: New Component
with Relink Source or Extract to File, then Appearance, Physical Material and Cycle Component
Color, then Rename and Properties.
A flyout of a flyout that opened to the left opens to the left too instead of covering the
menu, Left back to the menu chooses the row that opened the flyout, ui.menuPick refuses a row
that only opens a flyout, and the icon audit checks every level. The guides and the control
protocol describe the new menu paths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Browser: ui.menu gives a flyout's rows their placeholder flag too
ui.menu set placeholder only on the top-level rows, so a client reading
a flyout row's placeholder got nothing. The icon audit now reads the flag
of the flyout rows instead of resolving their icons itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
## Start here
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Act when the code, docs and rules settle a question; ask only when it changes what users see. Local `plan/` notes are optional.
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Local `plan/` notes are optional.
## Hard rules (what enforces each)
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
- Never crash: no unwrap/expect/panic/unsafe in non-test code (unsafe allowlist in crash-safety), errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
- Never crash: no unwrap/expect/panic/unsafe in non-test code, errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
- New kernel cases go to solidcore: `cargo xtask lists`.
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
@@ -21,12 +21,14 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
- A design-format change is ONE new file in `crates/doc/src/format/pending/`: `scripts/test-format-pending.sh`.
- A command has input and output schemas (will become required; `schema/unconverted.txt` only shrinks): `skills/solidcraft-commands/SKILL.md`.
- New UI strings go in `assets/locales/pending/<slug>.json`, ATTRIBUTION rows in `attribution/pending/<slug>.md`: `cargo xtask lists`.
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line, and no second PR for an issue a lower-numbered one closes: `pr-checks.yml`.
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line: `pr-checks.yml`.
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR, never force-push `main`, no pushes just to catch up with `main`: `skills/solidcraft-contributing/SKILL.md`.
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR: `skills/solidcraft-contributing/SKILL.md`.
- A PR's title and first sentence make sense to someone who never saw your chat or the issue: `skills/solidcraft-contributing/SKILL.md`.
- Code, comments, commit messages and docs are in English, always: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
- Independent work runs in parallel, dependent work stays sequential: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
- Show observable changes with real screenshots or a recording, captured yourself.
- Run `cargo xtask ci` before every commit, without switching branches while it runs. `cargo xtask skills` keeps this file and `skills/` honest.
- Run `cargo xtask ci` before every commit. `cargo xtask skills` keeps this file and `skills/` honest.
@@ -82,7 +82,6 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf | IBM Corp. (IBM Plex Mono) | https://github.com/IBM/plex (via google/fonts), unmodified; licence text in assets/fonts/ibm-plex-mono/OFL.txt; the interface monospace font | SIL OFL 1.1 |
| assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf | IBM Corp. (IBM Plex Sans, variable wdth and wght) | https://github.com/IBM/plex (via google/fonts), unmodified; licence text in assets/fonts/ibm-plex-sans/OFL.txt; the default interface font | SIL OFL 1.1 |
| docs/book/src/generated/tool-preview.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/tool-preview.json`; gitignored) | MIT OR Apache-2.0 |
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
| assets/fonts/craft-ui-sc/CraftUI-SC.otf | Adobe / Google Inc. (Noto Sans SC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/SC Regular, glyph-subset for assets/locales/zh.json and renamed Craft UI SC (OFL reserved-name); licence text in assets/fonts/craft-ui-sc/OFL.txt; web UI Simplified Chinese | SIL OFL 1.1 |
| assets/fonts/craft-ui-tc/CraftUI-TC.otf | Adobe / Google Inc. (Noto Sans TC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/TC Regular, glyph-subset for assets/locales/zh-TW.json and renamed Craft UI TC (OFL reserved-name); licence text in assets/fonts/craft-ui-tc/OFL.txt; web UI Traditional Chinese | SIL OFL 1.1 |
@@ -147,7 +146,6 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/fonts/craft-ui-tc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-TC.otf | SIL OFL 1.1 |
| assets/fonts/ibm-plex-mono/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/ibm-plex-sans/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/icons/app/linux/exports/hicolor/128x128/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/16x16/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/24x24/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
@@ -203,7 +201,6 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/shaders/viewport.wgsl | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/ui/palettes.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-dark.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/gruvbox-light.json | SolidCraft contributors | original SolidCraft theme file mapping SolidCraft's UI colours to the Gruvbox palette's colour values (Gruvbox by Pavel Pertsev, https://github.com/morhetz/gruvbox, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/solidcraft-95.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
@@ -215,6 +212,7 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/styles/ui/themes/one-dark.json | SolidCraft contributors | original SolidCraft theme file with colours adapted from the One Dark palette (Atom One Dark by GitHub Atom, https://github.com/atom/atom, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/tokyo-night.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Tokyo Night by enkia, https://github.com/enkia/tokyo-night-vscode-theme, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/solarized-light.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/solarized-dark.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/nord.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Nord by Sven Greb and the Nord contributors, https://github.com/nordtheme/nord, MIT) | MIT OR Apache-2.0 |
| assets/styles/ui/themes/high-contrast.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
| assets/styles/ui/themes/graphite.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
@@ -799,6 +797,52 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
| docs/book/src/media/ui-tour/navigate.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (`--sample`, default dark theme, software renderer) orbiting, panning and zooming, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| assets/locales/pending/sketch-tool-hints.json | carlymx | Original sketch tool hints and engine error translations | MIT OR Apache-2.0 |
| assets/locales/pending/primitive-pick-field-tips.json | SolidCraft contributors | Original primitive pick-field tooltip translations | MIT OR Apache-2.0 |
| assets/locales/pending/ribbon-panel-hover.json | SolidCraft contributors | Original ribbon panel menu preference labels and translations | MIT OR Apache-2.0 |
| docs/screenshots/box-insert-designer.gif | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (2.5× playback, 800 px wide) | MIT OR Apache-2.0 |
| docs/screenshots/box-insert-designer.mp4 | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (1280×720) | MIT OR Apache-2.0 |
| assets/locales/pending/agent-http-consent.json | SolidCraft contributors | Original endpoint-specific HTTP consent messages and translations | MIT OR Apache-2.0 |
| assets/locales/pending/ground-shadow-placement.json | SolidCraft contributors | Original ground-shadow placement preference messages and translations | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/components.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making components from two original hinge-leaf bodies, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/joint.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) creating a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/drag.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dragging a jointed hinge leaf, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/assemblies/drive.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) driving a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/extrude.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) extruding a sketch's profile, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/shell.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) shelling a block, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/fillet.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) filleting four edges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/edit.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) editing a feature from the timeline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/hole.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) placing a hole, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/features/pattern.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) patterning a hole around an axis, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/parameters/dialog.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer) showing the Parameters dialog, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/files/menu.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`--sample`, default dark theme, software renderer) showing the File menu, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/getting-started/start.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing the start page with its built-in sample thumbnails, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/getting-started/sample.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) opening the built-in Sample Plate from the start page, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sheet-metal/base.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making a sheet metal base flange, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sheet-metal/flange.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) adding sheet metal edge flanges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sheet-metal/flat.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) showing a sheet metal flat pattern, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/create.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) starting a sketch on the Top plane, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/draw.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) drawing a rectangle and a circle with typed values, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/dimension.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dimensioning a sketch until it is fully constrained, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/parts.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing a fully constrained sketch, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/offset.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) offsetting a sketch outline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| docs/book/src/media/sketching/trim.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) trimming a notch in a sketch, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
| assets/locales/ca.json | carlymx | Catalan translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
| assets/locales/pending/sketch-mirror-auto-line.json | SolidCraft contributors | Original Mirror preference label and translations | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-01-extrude5-edge-frame.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-04-preview-outer-l11.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-05-preview-inner-l8.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-06-result-outer-120.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-07-preview-360.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-08-cancelled.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-13-colliding-id.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-14-surface-l1.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/demo-15-surface-result.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| docs/screenshots/7452/revolve-selected-edge.mp4 | SamD98 | screen recording of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
| assets/locales/bg.json | Yasen-55 | Original translation | MIT OR Apache-2.0 |
| crates/sketch/assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in crates/sketch/assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
| crates/sketch/assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/locales/pending/drag-to-zoom.json | SolidCraft contributors | Original drag-to-zoom labels and translations | MIT OR Apache-2.0 |
| `assets/locales/pending/hole-group-folder.json` | SolidCraft contributors | Original Hole grouping preference labels and translations | MIT OR Apache-2.0 |
| assets/locales/pending/press-pull-dialog-title.json | SolidCraft contributors | Original Press Pull dialog title translations for SolidCraft's English catalog; no external text used | MIT OR Apache-2.0 |
## Code adapted from sibling projects
@@ -868,11 +912,10 @@ copied into SolidCraft.
| Directory | Source | Licence | Changes |
|---|---|---|---|
| vendor/accesskit_macos | accesskit_macos 0.26.3, https://github.com/AccessKit/accesskit (The AccessKit contributors) | MIT OR Apache-2.0 | the subclassing adapter and its tests from 0.27.2: dropping the adapter keeps AppKit's key-value observers (vendor/README.md) |
## Vendored renderer compatibility fix
`vendor/egui-wgpu` is egui-wgpu 0.36.2 from https://github.com/emilk/egui
`vendor/egui-wgpu` is egui-wgpu 0.37.0 from https://github.com/emilk/egui
(MIT OR Apache-2.0; both original licence texts are included). Rust sources and
its original `egui.wgsl` shader are retained; no images or other artwork are copied.
SolidCraft's narrow patch selects a safe 1x MSAA path on the chosen OpenGL adapter
@@ -225,6 +225,12 @@ completed drawings publish atomically. The browser still exports synchronously.
## Render foundation
- Wood surface images (#1345): native CPU and GPU opaque rendering share physical
repeat frames and filtered mip chains. Imported maps retain exact bytes and
declared provenance in the design, reopen offline, and expose grain-angle
controls. Scalar face appearances suppress body maps. Planar projection and
CPU encoding/optical limits remain explicit; see [portable surfaces](docs/render-portable-surfaces-and-stages.md).
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
- Bounded linear film accumulation (#1696) supplies caller-admitted mean/variance storage and complete-pass views; actual progressive consumers remain separate. See [the film contract](docs/render-film.md).
//! solidcraft-cli mcp setup [--dry-run] [--client NAME] [--live] (alias: mcp install) add it to OpenCode, Claude Desktop, Claude Code, Cursor
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
@@ -62,9 +62,9 @@ struct Command {
}
/// `run`'s lines in the usage.
constUSAGE_RUN: &str=" solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
constUSAGE_RUN: &str=" solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict] [--quality standard|fine|finest]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--quality: how closely an STL, OBJ or 3MF --out follows curved faces, as file.export's quality: a chord of\n 2e-4, 5e-5 or 1e-5 of each body's size, never under 1 µm, default standard)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
/// `eval`'s lines in the usage.
constUSAGE_EVAL: &str=" solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile and --profile-out as for run)\n";
constUSAGE_EVAL: &str=" solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] [--validation each_step|each_feature|off] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile, --profile-out and --validation as for run)\n";
/// `script`'s lines in the usage.
constUSAGE_SCRIPT: &str=" solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
constUSAGE_MCP: &str=" solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu]\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
constUSAGE_MCP: &str=" solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu] [--compact]\n (--compact, SOLIDCRAFT_MCP_COMPACT=1 or the app's Preferences › AI (with --connect): compact tool output for\n agents: inspect_design summarises sketches and takes `sketches` by name and a `bodies` filter, and tool JSON\n is not pretty-printed; off by default)\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
/// `crashes`'s lines in the usage.
constUSAGE_CRASHES: &str=" solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
/// Runs `solidcraft-cli eval` (and its `inspect` alias), opening the input design or script with the requested `--validation` mode and reporting both `inspect.measure` and `document.inspect`.
/* App surfaces (keep in sync with assets/styles/ui/palettes.rs; lint_icons.py checks): --card is the ribbon surface `toolbar` the icons lie on, --bg the page (`panel` in light, `viewport_top` in dark), --line `border`, --fg `text`, --mut `text_dim`. */
/* App surfaces (keep in sync with crates/theme/src/palettes.rs; lint_icons.py checks): --card is the ribbon surface `toolbar` the icons lie on, --bg the page (`panel` in light, `viewport_top` in dark), --line `border`, --fg `text`, --mut `text_dim`. */
"help.sketch.insert_svg":"Učitava putanje SVG crteža u skicu, skalirane, zakrenute i postavljene gdje zadate. Koristite ga da logotip ili vektorski obris pretvorite u krive koje možete ekstrudirati.",
"help.sketch.inspect":"Otvara izvještaj o otvorenoj skici: koliko je stepeni slobode preostalo i je li potpuno ograničena. Koristite ga da saznate šta se još pomjera prije nego što završite skicu.",
"help.sketch.intersect":"Dodaje krive ili tačke gdje tijela, lica, ivice ili druge skice sijeku ravan skice. Ostaju povezane i prate model kada se promijeni.",
"help.sketch.intersection_curve":"Pravi 3D krive tamo gdje se dva tijela ili lica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
"help.sketch.intersection_curve":"Pravi 3D krive tamo gdje se dva tijela, lica ili krive drugih skica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
"help.sketch.isoparametric_curve":"Pravi 3D krivu u smjeru U ili V lica kroz tačku koju izaberete, povezanu s licem. Koristite je kao putanju ili liniju razdvajanja koja prati površ.",
"help.sketch.line":"Crta povezane linije tačku po tačku, kao otvoreni lanac ili zatvoren obris. To je osnovni alat za većinu profila.",
"help.sketch.line.midpoint":"Crta liniju od njene središnje tačke do jednog kraja, a drugi kraj se preslikava. Koristite je za linije centrirane na tački, npr. simetričnu ivicu.",
"material.editor.save_or_discard":"Sačuvajte ili odbacite nacrt materijala prije dodjele materijala.",
"material.editor.saved":"Materijal je sačuvan. Dodjele tijelima ostaju nepromijenjene.",
"material.editor.scope":"Sačuvaj sprema ovaj materijal bez dodjele tijelima. Prazna svojstva i uslovi ostaju nepoznati. Gustina s kartice ne mijenja dodijeljenu gustinu ni masu.",
"help.sketch.insert_svg":"Læser stierne i en SVG-tegning ind i en skitse, skaleret, drejet og placeret, som du angiver. Brug den til at gøre et logo eller en vektorkontur til kurver, du kan ekstrudere.",
"help.sketch.inspect":"Åbner en rapport om den åbne skitse: hvor mange frihedsgrader der er tilbage, og om den er fuldt bundet. Brug den til at finde ud af, hvad der stadig kan bevæge sig, før du afslutter skitsen.",
"help.sketch.intersect":"Tilføjer de kurver eller punkter, hvor emner, flader, kanter eller andre skitser krydser skitseplanet. De forbliver linkede og følger modellen, når den ændres.",
"help.sketch.intersection_curve":"Opretter 3D-kurver, hvor to emner eller flader mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
"help.sketch.intersection_curve":"Opretter 3D-kurver, hvor to emner, flader eller kurver fra andre skitser mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
"help.sketch.isoparametric_curve":"Opretter en 3D-kurve langs en flades U- eller V-retning gennem et punkt, du vælger, linket til fladen. Brug den som bane eller delelinje, der følger overfladen.",
"help.sketch.line":"Tegner forbundne linjer punkt for punkt som en åben kæde eller en lukket kontur. Det er det grundlæggende værktøj til de fleste profiler.",
"help.sketch.line.midpoint":"Tegner en linje fra dens midtpunkt ud til den ene ende, og den anden ende spejles. Brug den til linjer centreret om et punkt, f.eks. en symmetrisk kant.",
"material.editor.save_or_discard":"Gem eller kassér materialeudkastet, før du tildeler et materiale.",
"material.editor.saved":"Materialet er gemt. Tildelinger til emner er uændrede.",
"material.editor.scope":"Gem gemmer materialet uden at tildele det til emner. Tomme egenskaber og betingelser forbliver ukendte. Kortets densitet ændrer ikke den tildelte densitet eller massen.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Eksempelplade",
"sample.vents.about":"Slidser i et rektangulært mønster: Rækker, kolonner og deling er parametre",
"sample.vents.name":"Ventilationsplade",
"scale.per_axis":"Per akse",
"scale.uniform":"Ensartet",
"search.command_keys":"Enter kør Shift+Enter kør og hold åben Esc luk",
"Surface, or faces to turn over":"Fläche oder umzudrehende Flächen",
"Surfaces":"Flächen",
"Swap Ends":"Enden vertauschen",
@@ -641,6 +645,7 @@
"Target":"Ziel",
"Target Body":"Zielkörper",
"Target Face (optional)":"Zielfläche (optional)",
"Target edges":"Zielkanten",
"Tear":"Reißen",
"Technical details":"Technische Details",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.":"Die gestrichelte Skizze ist eine gespeicherte Ebenenführung. Wählen Sie eine neue Fläche, um die Anbindung zu reparieren.",
@@ -648,6 +653,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane":"Die Richtung muss eine Achse oder eine gerade Skizzenkurve in der Skizzenebene sein",
"The mirror line cannot also be mirrored":"Die Spiegellinie kann nicht gleichzeitig gespiegelt werden",
"The mirror line must be a straight curve of this sketch, not an origin axis":"Die Spiegellinie muss eine gerade Kurve dieser Skizze sein, keine Ursprungsachse",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"Die Ursprungsachse steht senkrecht auf der Skizzenebene und hat daher keine Linie in ihr",
"The saved author occurrence is missing. Select a valid author placement before rebinding.":"Das gespeicherte Autorvorkommen fehlt. Wählen Sie vor dem erneuten Anbinden eine gültige Autorplatzierung.",
"Then":"Then",
"These features will be deleted:":"Diese Merkmale werden gelöscht:",
@@ -689,6 +695,7 @@
"Unsuppress Selected Folders":"Unterdrückung der ausgewählten Ordner aufheben",
"Updating the preview":"Vorschau wird aktualisiert",
"Use active author placement":"Aktive Autorplatzierung verwenden",
"Use axis direction only":"Nur Achsenrichtung verwenden",
"Use selected face as fixed plane":"Ausgewählte Fläche als feste Ebene verwenden",
@@ -739,6 +746,9 @@
"a picked edge's body is gone":"der Körper einer gewählten Kante existiert nicht mehr",
"add a key for a joint first":"zuerst einen Schlüssel für ein Gelenk hinzufügen",
"ai.connected_toast":"SolidCraft ist jetzt in {clients} verfügbar. Starten Sie diese neu, um es zu nutzen (Einstellungen > Assistent).",
"ai.http.allow":"Unsicheres HTTP erlauben",
"ai.http.confirm":"Für diesen Endpunkt erlauben",
"ai.http.warning":"Der API-Schlüssel und die Unterhaltung werden unverschlüsselt gesendet. Jeder mit Zugriff auf das Netzwerk kann sie lesen oder verändern. Verwenden Sie dies nur in einem vertrauenswürdigen Netzwerk.",
"ai.prefs.allow_control_note":"Ein lokaler Kanal (MCP). Nichts verlässt diesen Computer.\n\nÖffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
"ai.prefs.availability":"Verfügbarkeit",
@@ -784,6 +794,7 @@
"browser.calibrate_click_a":"Klicken Sie im Bild auf die Position von Punkt A oder geben Sie dessen Koordinaten ein.",
"browser.calibrate_click_b":"Klicken Sie im Bild auf die Position von Punkt B oder geben Sie dessen Koordinaten ein.",
"browser.calibrate_title":"Kalibrieren: {name}",
"browser.canvas_locked":"Die Leinwand ist gesperrt. Entsperren Sie sie zuerst.",
"browser.capture_many":"{count} Komponenten wurden verschoben, ihre Positionen sind aber nicht erfasst.",
"browser.capture_note":"„Position erfassen“ behält die neue Position bei; „Position zurücksetzen“ stellt die vorherige wieder her.",
"browser.capture_one":"Eine Komponente wurde verschoben, ihre Position ist aber nicht erfasst.",
"file.open.portal_fallback.xwayland":"Die geöffnete Dateiauswahl ist nicht die des Desktops: Unter XWayland lehnt das Portal das Elternfenster ab.",
"file_load.built":"Bisher erstellt: {built} von {total} Merkmalen.",
"file_load.converting":"Volumenkörper {done} von {total} wird konvertiert",
"file_load.inserted":"{name} eingefügt",
"file_load.inserting":"Geometrie wird eingefügt",
"file_load.link_item":"{name} ({done} von {total})",
"file_load.opened":"{name} geöffnet",
"file_load.opened_in_tab":"{name} in neuem Tab geöffnet",
"file_load.opening":"Design wird geöffnet",
"file_load.preparing_thumbnail":"Vorschaubild wird vorbereitet...",
"file_load.preparing_view":"Ansicht wird vorbereitet...",
@@ -1456,6 +1491,7 @@
"file_load.reading_percent":"Datei wird gelesen... {percent} %",
"file_load.rebuilding":"Merkmal {done} von {total} wird neu aufgebaut",
"file_load.recovering":"Design wird wiederhergestellt",
"file_load.reloading_cloud":"Punktwolke wird neu geladen",
"file_load.step_build":"Modell wird aufgebaut",
"file_load.step_feature":"Merkmal {done} von {total}",
"file_load.step_part":"Teil {done} von {total}",
@@ -1683,7 +1719,7 @@
"help.sketch.insert_svg":"Liest die Pfade einer SVG-Zeichnung in eine Skizze ein, skaliert, gedreht und an der gewünschten Stelle. Damit machen Sie aus einem Logo oder einer Vektorkontur Kurven, die sich extrudieren lassen.",
"help.sketch.inspect":"Öffnet einen Bericht zur offenen Skizze: wie viele Freiheitsgrade übrig sind und ob sie vollständig bestimmt ist. Damit sehen Sie, was sich noch bewegt, bevor Sie die Skizze abschließen.",
"help.sketch.intersect":"Fügt die Kurven oder Punkte hinzu, an denen Körper, Flächen, Kanten oder andere Skizzen die Skizzenebene schneiden. Sie bleiben verknüpft und folgen Änderungen am Modell.",
"help.sketch.intersection_curve":"Erzeugt 3D-Kurven, wo sich zwei Körper oder Flächen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
"help.sketch.intersection_curve":"Erzeugt 3D-Kurven, wo sich zwei Körper, Flächen oder Kurven anderer Skizzen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
"help.sketch.isoparametric_curve":"Erzeugt eine 3D-Kurve in U- oder V-Richtung einer Fläche durch einen gewählten Punkt, mit der Fläche verknüpft. Als Pfad oder Trennlinie, die der Fläche folgt.",
"help.sketch.line":"Zeichnet verbundene Linien Punkt für Punkt, als offene Kette oder geschlossene Kontur. Das Grundwerkzeug für die meisten Profile.",
"help.sketch.line.midpoint":"Zeichnet eine Linie von ihrem Mittelpunkt zu einem Ende, das andere Ende gespiegelt. Für Linien, die auf einem Punkt mittig liegen, etwa eine symmetrische Kante.",
@@ -1695,6 +1731,7 @@
"help.sketch.pattern.circular":"Kopiert Skizzengeometrie um einen Mittelpunkt, über einen vollen Kreis oder einen festen Winkel. Für Lochkreise, Speichen und Zähne.",
"help.sketch.pattern.rectangular":"Kopiert Skizzengeometrie in Reihen und Spalten mit festen Abständen. Für Raster aus Bohrungen, Schlitzen oder Lüftungsöffnungen.",
"help.sketch.point":"Setzt einen Skizzenpunkt. Als Bezug für Maße, Bedingungen, Bohrungsmitten oder spätere Elemente.",
"help.sketch.point.divide":"Setzt Punkte, die die gewählten Kurven in gleich lange Abschnitte teilen. Die Punkte bleiben auf jeder Kurve und behalten ihren Abstand, wenn sie sich ändert.",
"help.sketch.point3d":"Setzt bei eingeschalteter 3D-Skizze einen Punkt im Raum. Als Bezug für 3D-Kurven und Pfade.",
"help.sketch.polygon.circumscribed":"Zeichnet ein regelmäßiges Polygon um einen Kreis, bemessen vom Mittelpunkt zur Mitte einer Seite. Wenn die Schlüsselweite bekannt ist, etwa bei einer Sechskantmutter.",
"help.sketch.polygon.edge":"Zeichnet ein regelmäßiges Polygon aus den beiden Enden einer Seite. Wenn Länge und Lage einer Seite bekannt sind.",
@@ -1794,6 +1831,7 @@
"help.view.visual_style":"Schaltet um, wie das Modell gezeichnet wird: schattiert mit oder ohne Kanten, Drahtmodell oder mit verdeckten Kanten. Drahtmodell zum Hineinschauen, schattiert für ein sauberes Bild.",
"material.editor.scope":"Speichern legt dieses Material ab, ohne es Körpern zuzuweisen. Leere Eigenschaften und Bedingungen bleiben unbekannt. Die Dichte im Datenblatt ändert weder die zugewiesene Dichte noch die Masse.",
"network.always_allow":"Diesen Server immer erlauben",
@@ -2276,6 +2316,8 @@
"prefs.bottom_centre":"Unten mittig",
"prefs.bottom_right":"Unten rechts",
"prefs.browser":"Browser",
"prefs.browser_body_features":"Features unter Körpern",
"prefs.browser_body_features_note":"Jeder Körper im Browser lässt sich aufklappen und zeigt die Features der Zeitleiste, die ihn erzeugt haben, in deren Reihenfolge.",
"prefs.browser_components_open":"Komponenten zu Beginn aufgeklappt",
"prefs.browser_components_open_note":"Jede Komponente im Browser ist wie bisher von Anfang an aufgeklappt, statt zugeklappt zu bleiben, bis du sie aufklappst.",
"prefs.browser_eye_left":"Auge neben dem Aufklapppfeil",
"prefs.design_tools.hole_group_help":"In einem Schritt erstellte Bohrungen teilen sich einen Ordner im Verlauf, zunächst geschlossen; öffnen Sie ihn, um eine Bohrung einzeln zu bearbeiten.",
"prefs.design_tools.hole_moves_help":"Erneutes Auswählen der Fläche verschiebt die Bohrung, statt sie zu entfernen.",
"prefs.design_tools.hole_several":"Mehrere Bohrungen auf einer Fläche",
"prefs.design_tools.hole_several_help":"Eine Auswahl an anderer Stelle der Fläche fügt eine weitere Bohrung hinzu; die Auswahl einer Bohrung entfernt sie.",
"prefs.design_tools.hole_snaps_help":"Die Mitte einer Fläche und die Mittelpunkte ihrer kreisförmigen Kanten.",
"prefs.extra_navigation":"Mit Strg und gedrückter linker Maustaste auf einer freien Fläche drehen. Leertaste gedrückt halten, um die Ansicht zu verschieben.",
"prefs.extrude_arrow_face_stops":"Extrusionspfeil hält an Flächen",
"prefs.extrude_arrow_face_stops_note":"Beim Ziehen des Abstandspfeils von Extrusion hält er an ebenen Flächen parallel zum Profil, etwa an der Rückseite eines Blocks, in den geschnitten wird, und hebt die Fläche hervor. Aus: Der Pfeil bewegt sich nur in runden Schritten.",
"prefs.extrude_view_isometric":"Beim Öffnen von Extrudieren isometrisch ausrichten",
"prefs.extrude_view_return":"Beim Schließen von Extrudieren zur vorigen Ansicht zurück",
"prefs.follows_os":"Wie im Betriebssystem",
@@ -2388,9 +2437,17 @@
"prefs.graphics_page.surfaces_edges":"Flächen und Kanten",
"prefs.graphics_page.view_bar":"Auch in der Ansichtsleiste",
"prefs.graphics_presets_note":"Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
"prefs.ground_shadow_follows_model":"Bodenschatten folgt dem Modell",
"prefs.ground_shadow_follows_model_help":"Den Schatten unter dem niedrigsten sichtbaren Körper platzieren. Wenn deaktiviert, bleibt der Schatten angehobener Modelle auf der Bodenebene durch den Ursprung.",
"prefs.hide_constraint_glyphs_zoomed_out":"Bindungssymbole bei starkem Herauszoomen ausblenden",
"prefs.hide_constraint_glyphs_zoomed_out_note":"Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
"prefs.history":"Verlauf",
"prefs.history_bar_groups":"Verlaufsleiste in Gruppen falten",
"prefs.history_bar_groups_note":"In einem langen Verlauf wird eine Folge von Features einer Komponente zu einem Chip gefaltet, und Merkmalsordner beginnen gefaltet. Eine eingefügte STEP-Baugruppe kommt in einen Group-Ordner. Aus zeigt jedes Feature, öffnet Ordner und legt keinen Ordner an.",
"prefs.history_folder_kind_icon":"Ordnersymbol aus seinen Features",
"prefs.history_folder_kind_icon_note":"Ein Ordner im Verlauf, dessen Features alle von einer Art sind, zeigt deren Symbol.",
"prefs.history_group_joints":"Gelenke im Verlauf gruppieren",
"prefs.history_group_joints_note":"Zwei oder mehr Gelenke erscheinen als eine Schaltfläche mit ihrer Anzahl; ein Klick zeigt jedes einzeln. Aus zeigt jedes Gelenk einzeln.",
"prefs.history_origin_bodies":"Ursprung und Körper oben zeigen",
"prefs.history_origin_bodies_note":"Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
"prefs.history_play_button":"Schaltfläche „Verlauf abspielen“ in den Verlaufssteuerelementen",
@@ -2423,6 +2480,8 @@
"prefs.layout_page.card":"Karte",
"prefs.layout_page.column":"Spalte",
"prefs.layout_page.hover":"Bei Mauszeiger",
"prefs.layout_page.hover_panel_menus":"Bereichsmenüs beim Darüberfahren wechseln",
"prefs.layout_page.hover_panel_menus_help":"Klicken Sie auf einen Bereichsnamen, um das Menü zu öffnen. Bewegen Sie den Zeiger auf einen anderen Bereichsnamen, um das Menü zu wechseln. Außerhalb des Menübands und des geöffneten Menüs schließt es sich.",
"prefs.move_drag_highlights":"Hervorheben, was unter dem Zeiger liegt, während die Pfeile von Verschieben eine Komponente ziehen",
"prefs.move_drag_highlights_tip":"Aus: Solange die Pfeile von Verschieben eine Komponente ziehen, wird nichts unter dem Zeiger hervorgehoben; so bleiben große Baugruppen flüssig. Mit einem Schnitt oder einer Analyse wird es trotzdem hervorgehoben, und ein entlang seiner Gelenke gezogenes Teil hebt nie etwas hervor.",
"prefs.navigation":"Navigation",
"prefs.navigation.axes_signed":"Achsen",
"prefs.navigation.cube_compact":"Würfel",
@@ -2484,6 +2545,7 @@
"prefs.origin_plane_names":"Namen der Ursprungsebenen",
"prefs.origin_plane_names_note":"Namen in Browser, Dialogen und Listen. {top} ist XY, {front} ist XZ und {right} ist YZ, mit Z nach oben. Befehle, Skripte und gespeicherte Konstruktionen behalten XY, XZ und YZ.",
"prefs.overall_speed":"Gesamtgeschwindigkeit",
"prefs.panel_layout.recovery":"Gespeicherte Panel-Layouts konnten nicht wiederhergestellt werden. Die gespeicherten Daten bleiben erhalten; Klassisch bleibt verfügbar.",
"prefs.parameter_list_rows":"Zeilen der Parameterliste",
"prefs.parameter_list_rows_tip":"Maximale Anzahl der Parameter, die Pfeil abwärts in einem leeren Ausdrucksfeld auflistet.",
"prefs.quick_measure":"Schnellmessung in der Ecke der Ansicht",
"prefs.quick_measure_note":"Zeigt unten rechts in der Ansicht die Größe der Auswahl an: bei einer Kante die Länge, bei einer kreisförmigen Kante zusätzlich Durchmesser (Ø) und Radius (R), bei einer Fläche den Flächeninhalt, bei einem Punkt die Position, bei zwei Elementen Abstand und Winkel sowie bei mehreren Kanten oder Flächen die Gesamtlänge bzw. Gesamtfläche. Ein Klick kopiert den Messwert.",
"prefs.remember_dialog_values.help":"Standardmäßig aus. Wenn aktiviert, werden nach erfolgreicher Ausführung eines Befehls seine sicheren Werte und Optionen beim nächsten Öffnen desselben Befehls wiederverwendet, bis SolidCraft geschlossen wird.",
"prefs.render_legacy_image_defaults_help":"Den bisherigen manuellen Ablauf beibehalten: neue Setups mit CPU-Raster, feste Abtastung beim CPU-Raytracing und manuell geöffnete Ergebnisse. Beim Wechsel zu Render die Ansicht neu einpassen, Aufnahmen ohne Wiederherstellung ihrer Kamera wechseln und die bisherige Bildausrichtung und Navigation verwenden. Automatische Live-Bildrückmeldungen deaktivieren. Gespeicherte Setups bleiben unverändert.",
"prefs.render_previous_geometry_budget":"Bisheriges Geometriebudget für die Render-Vorschau verwenden",
"prefs.render_previous_geometry_budget_help":"Stellt die bisherige feste Obergrenze für die Geometrie wieder her (512 MiB auf 64-Bit-Desktop-Systemen, andernfalls 128 MiB) anstelle des geräteabhängigen Budgets.",
"prefs.reports_saved":"{n} gespeichert",
@@ -2546,6 +2612,8 @@
"prefs.select_other_hidden":"Verborgene Geometrie mit „Andere auswählen“ auswählen",
"prefs.select_other_hidden_help":"Ein: Verborgene Körper, Skizzen und verdeckte Kanten nahe dem Zeiger einbeziehen. Aus: Die ursprüngliche Liste sichtbarer Geometrie direkt unter dem Zeiger verwenden.",
"prefs.shared_panel_layout_help":"Browser, Verlauf und Assistent in verschiebbaren Panels anzeigen. Die Änderung wird beim Anwenden der Einstellungen wirksam.",
"prefs.shift":"Umschalt",
"prefs.shift_add_ctrl_toggle":"Umschalt fügt nur hinzu; Strg / Cmd schaltet die Auswahl um",
"prefs.shift_add_note":"Aktiviert: Umschalt fügt Elemente zur Auswahl hinzu, Strg / Cmd schaltet ihre Auswahl um. Deaktiviert (Standard): Beide Tasten schalten die Auswahl um.",
@@ -2568,6 +2636,8 @@
"prefs.sketch.drawing":"Zeichnen",
"prefs.sketch.ellipse_axes":"Ellipsen mit ihren Achsen zeichnen",
"prefs.sketch.ellipse_axes_note":"Haupt- und Nebenachse als Konstruktionslinien, damit Halbachsen und Achsen bemaßt werden können. Aus: nur die Ellipse, und D darauf ergibt ihren Nebenradius, wohin der Wert auch gezogen wird, gezeichnet und mit R beschriftet wie bisher.",
"prefs.sketch.escape_twice_finishes":"Doppeltes Esc beendet die Skizze",
"prefs.sketch.escape_twice_finishes_note":"Zweimal schnell Esc drücken beendet die Skizze. Aus: Esc bricht nur das Werkzeug oder die Auswahl ab, und Skizze beenden schließt die Skizze.",
"prefs.sketch.exit_unless_rotated":"Außer bei Drehung",
@@ -2601,8 +2671,9 @@
"prefs.sketch_exit_view_note":"Legt fest, zu welcher Ansicht nach „Skizze beenden“, „Abbrechen“ oder zweimaligem Drücken der Esc-Taste gewechselt wird.",
"prefs.sketch_face_activates_part":"Skizzieren auf der Fläche eines Teils aktiviert dieses Teil",
"prefs.sketch_face_activates_part_note":"Eine Skizze, die auf einer Fläche am Körper einer anderen Komponente begonnen wird, aktiviert zuerst diese Komponente, sodass die Skizze zu ihr gehört und im Browser bei diesem Teil bleibt. Das Teil bleibt danach aktiv. Aus: Die Skizze gehört zur aktiven Komponente.",
"prefs.sketch_mirror_auto_line":"Vorausgewählte Konstruktionslinie zum Spiegeln verwenden",
"prefs.sketch_open_look_at":"Beim Öffnen einer Skizze auf sie ausrichten",
"prefs.sketch_open_look_at_note":"Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Ansehen“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
"prefs.sketch_open_look_at_note":"Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Senkrecht auf“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
"prefs.sketch_recent_snap":"Automatisch an zuletzt verwendete Mittelpunkte fangen",
"prefs.sketch_recent_snap_note":"Fängt den Zeiger beim Zeichnen dort ein, wo sich die Hilfslinien der beiden zuletzt verwendeten Kreismittelpunkte oder Kantenmitten kreuzen. Aus: die Hilfslinien bleiben sichtbar und der Zeiger bewegt sich nicht.",
"prefs.sketch_selection":"Skizzen und Auswahl",
@@ -2652,6 +2723,8 @@
"prefs.structure.work":"Arbeit",
"prefs.supersampling_brief":"Rendert die Ansicht größer und verkleinert sie dann: sehr scharf, aber rechenintensiver.",
"prefs.tab_accent_note":"Die Akzentfarbe richtet sich nach dem aktiven Tab.",
"prefs.tab_double_click_renames":"Entwurfsregisterkarte per Doppelklick umbenennen",
"prefs.tab_double_click_renames_note":"Geben Sie den neuen Namen ein und drücken Sie Enter oder klicken Sie woanders hin, um ihn zu übernehmen, oder Esc zum Abbrechen. Aus: Ein Doppelklick auf eine Registerkarte maximiert das Fenster, wie auf dem leeren Teil der Leiste.",
"prefs.theme_file":"Designdatei",
"prefs.theme_file_or_paste":"Datei oder Einfügen",
"prefs.theme_follow":"Einer Designdatei folgen",
@@ -2748,12 +2821,15 @@
"refused.join":"Konnte nicht mit {body} vereinigt werden.",
"render.adaptive.preview_help":"Pilotbilder bestimmen die Verteilung und sind vom Endbild ausgeschlossen. Produktionsiterationen nutzen ein eigenes Budget pro Pixel; die Iterationszahl ist keine einheitliche Samplezahl pro Pixel.",
"render.adaptive_help":"Nur GPU. Unabhängige Pilot-Samples werden verworfen. Anschließend werden pro Pixel 16 oder mehr Produktions-Samples zugeteilt, bis zu den Samples pro Pixel. Die Zuteilung richtet sich nach der RGB- und Abdeckungsvarianz; seltene Pfade können übersehen werden, und Pixel, die ihr Limit erreicht haben, können verrauscht bleiben. Der Abschlussbericht zählt den tatsächlichen Aufwand. Keine zertifizierte Garantie für ein bestimmtes Rauschniveau oder eine bestimmte Renderzeit.",
"render.adaptive_help":"Unabhängige Pilotsamples werden verworfen und bestimmen dann mindestens 16 Produktionssamples pro Pixel bis zum Wert Samples pro Pixel. CPU-Ressourcengrenzen umfassen beide Phasen. RGB- und Abdeckungsvarianz steuern die Verteilung; Pixel am Limit können verrauscht bleiben. Der Abschlussbericht zählt die tatsächlichen Samples.",
"render.adaptive_pending":"Die adaptive Zuteilung mit unabhängigen GPU-Pilot-Samples und bedingte Fresnel-Schichten sind verfügbar. Zertifizierte Konvergenz, Garantien für seltene Ereignisse und adaptiver Lichttransport auf der CPU bleiben nicht verfügbar.",
"render.adaptive_pending":"Adaptive Verteilung mit unabhängigen Pilotsamples ist für CPU und GPU verfügbar. Bedingte Fresnel-Schichten erfordern die GPU. Die statistische Verteilung bestätigt keine Konvergenz.",
"render.adaptive_pilots":"Pilot-Samples pro Pixel",
"render.adaptive_relative":"Zielwert für den relativen RGB-Fehler",
"render.add_ground":"Add fitted ground to setup",
@@ -2776,6 +2852,7 @@
"render.aging_metallic":"Metallanteil der Schicht",
"render.appearance_editor.glass":"Glas und Absorption",
"render.appearance_editor.glass_enable":"Physikalisches Glas für diesen Körper verwenden",
"render.appearance_editor.glass_note":"Geschlossenes Glas verwendet geometrische Abstände. Es kann nicht mit Klarlack oder Texturen auf demselben Körper kombiniert werden.",
@@ -2814,7 +2892,7 @@
"render.appearance_editor.replace":"Gespeicherte Optik dieses Körpers ersetzen",
"render.appearance_editor.revalidate":"Diesen Körper erneut prüfen",
"render.appearance_editor.save_reason":"Zum Speichern wird physikalisches Glas auf einem aktuellen GPU-Körper benötigt. Klarlack und lokale Bildtexturen bleiben vorübergehend.",
"render.appearance_editor.save_reason":"Zum Speichern Glas an einem aktuellen CPU- oder GPU-Körper aktivieren. Für Klarlack „Klarlack in Aufnahme speichern“ verwenden. Lokale Bilddateien bleiben temporär.",
"render.appearance_editor.saved":"Mit dieser Aufnahme gespeichert",
"render.appearance_editor.saved_state":"Diese Optik ist mit dieser Aufnahme gespeichert.",
"render.appearance_editor.scatter_reason":"Innere Streuung benötigt einen geschlossenen Körper.",
@@ -2860,9 +2938,13 @@
"render.base_metallic":"Metallanteil",
"render.base_opaque_only":"Dieser Editor lässt Körper undurchsichtig. Für optische Transmission verwenden Sie die gesonderten Glaseinstellungen.",
"render.batch_current_settings_help":"Verwendet die aktuellen Bildeinstellungen mit jeder gespeicherten Aufnahmekamera, eine Aufnahme nach der anderen.",
"render.batch_empty":"Vor dem Rendern aller Ansichten eine Ansicht speichern.",
"render.batch_limit":"Es können höchstens 32 Ansichten gerendert werden.",
"render.batch_owner_limit":"Es können höchstens 32 Dokument-Bildfolgen vorgehalten werden. Schließen Sie zuerst eine vorhandene Folge ab oder brechen Sie sie ab.",
"render.batch_preparing":"Nächste Aufnahme wird vorbereitet",
"render.batch_waiting_worker":"Warten auf die Rendering-Vorbereitung",
"render.bloom_enable":"Darstellungs-Bloom",
"render.bloom_help":"Kreativer Ausgabeglanz aus linearen Glanzlichtern mit drei normierten Durchläufen. Radius in Ausgabepixeln; Addition vor sRGB. Keine Linsen-, Volumen- oder Konvergenzsimulation. Das rohe EXR bleibt unverändert.",
"render.bloom_opaque":"Bloom benötigt eine deckende Ausgabe. Transparentes PNG wird abgelehnt, nicht still abgeflacht.",
@@ -2892,6 +2974,16 @@
"render.category_paint":"Lack",
"render.category_plastic":"Kunststoff",
"render.category_wood":"Holz",
"render.caustics_depth":"Kaustik-Pfadtiefe",
"render.caustics_enable":"Glaskaustiken durch Punkt- und Richtungslichter",
"render.caustics_help":"Die Photonenzahl bestimmt Rauschen und Aufwand. Der Sammelradius tauscht scharfe Details gegen eine glattere Schätzung. CPU benötigt indirekte Beleuchtung.",
"render.check_glass_cpu":"Die CPU unterstützt geprüfte ebene Glasscheiben und achsenparallele Boxen. Allgemeine geschlossene Netze, Verschachtelung und innere Streuung erfordern indirekte Beleuchtung. Durch geschlossenes Glas übertragene Schatten bleiben nicht unterstützt.",
"render.check_glass_gpu":"Die GPU unterstützt geprüfte getrennte Glasmedien oder ausdrücklich aktivierte geprüfte Verschachtelung. Dafür müssen die äußeren Körper konvex und die inneren streng enthalten sein; Grenzen dürfen sich nicht berühren.",
"render.check_media_remaining":"Dichtegitter, Displacement, vollständige spektrale Integration und fortgeführte Polarisation bleiben nicht verfügbar.",
"render.check_media_scope":"Aktive innere Streuung benötigt ein geschlossenes Glasmedium ohne gerade Schattenübertragung und ohne Rauschreduzierung. Die GPU benötigt außerdem eine Fresnel-Schicht. Aktive äußere Atmosphäre kann nicht mit Glas oder Oberflächen-Rauschreduzierung kombiniert werden.",
"render.check_opaque":"Transparenz: nur undurchsichtige Oberflächen; Hintergrund-Alpha wird unterstützt.",
"render.check_sorted_alpha":"Transparenz: Näherung mit sortiertem Alpha.",
"render.coat_remove_note":"Entfernt den Klarlack dieses Ziels aus der Aufnahme. Geometrie, Aussehen und physikalische Materialien bleiben unverändert.",
"render.coat_revalidate_saved":"Der gespeicherte Klarlack wurde geändert. Diese Änderung verwerfen und das Ziel vor der nächsten Bearbeitung erneut öffnen.",
"render.coat_save":"Klarlack in Aufnahme speichern",
"render.coat_saved_help":"Klarlack fügt undurchsichtigen Flächen eine glänzende Schicht hinzu. Mit der Aufnahme speichern. Glas kann nicht dasselbe Ziel verwenden. Auf der CPU dürfen auch benutzerdefinierte Reflexions- und Anisotropieüberschreibungen dieses Ziel nicht verwenden.",
"render.cpu_celestial_adopt_label":"CPU-Raytracing mit indirekter Beleuchtung",
"render.cpu_celestial_path_required":"Endliche Sonne und Verlaufshimmel verwenden indirekte CPU-Beleuchtung.",
"render.cpu_celestial_switch":"Wähle CPU-Raytracing mit indirekter Beleuchtung, um diese Sonne oder diesen Himmel beizubehalten.",
"render.cpu_celestial_unportable":"Behalte GPU-Pathtracing für die aktuellen Transmissions-Sample-Einstellungen bei.",
"render.cpu_check_capture_help":"Prüfen und Starten verifizieren dieselbe Datei-Prüfsumme oder verwenden das eingebettete HDR. Jedes Bild behält seine erfasste Quelle.",
"render.cpu_check_changed":"Die Einstellungen oder das Modell haben sich nach dieser Prüfung geändert. Prüfen Sie erneut, bevor Sie rendern.",
"render.cpu_constant_direct_help":"Ohne indirekte Beleuchtung kann diese konstante Quelle in Reflexionen und durch Glas sichtbar sein. Sie beleuchtet keine undurchsichtigen Flächen. Schalte indirekte Beleuchtung ein, um sie als Szenenlicht zu nutzen.",
"render.cpu_constant_enable_indirect_plan":"Pfadtiefe {depth}; progressives Sampling mit {spp} Samples/Pixel (Seed {seed}); ein Primärstrahl pro Sample; Raytracing-Schatten. Bildgröße, CPU-Budget und andere Einstellungen bleiben erhalten.",
"render.cpu_constant_help":"Konstante Strahldichte ist für reflektierte oder durchgelassene Strahlen sichtbar. Sie liefert keine direkte diffuse Beleuchtung.",
"render.cpu_custom_directions":"Vorgabe durch eigene Richtungslichter ersetzen",
"render.cpu_denoise_help":"Verringert das Rauschen bei jeder Bildaktualisierung. Feine Details und Übergänge der Tiefenschärfe können weicher werden. Die EXR-Rohdaten bleiben unverändert. Nachträgliche Anpassungen eines fertigen Bildes verwenden dessen Rohbild und wenden die Rauschreduzierung nicht erneut an.",
"render.cpu_direction":"Richtung zum Licht (X, Y, Z)",
"render.cpu_exposure_help":"Die CPU-Belichtung muss zwischen −16 und 16 EV liegen. Eine GPU-Belichtung außerhalb dieses Bereichs bleibt erhalten, bis Sie sie ändern.",
"render.cpu_hdr_help":"Radiance-HDR-Datei (.hdr), Panorama im Format 2:1, bis zu 2048 × 1024 Pixel und 16 MiB. Für die CPU müssen die sRGB/D65-Primärfarben ausdrücklich in der Datei angegeben sein. Dateien ohne Angabe der Primärfarben sowie EXR-Dateien werden abgelehnt.",
"render.cpu_hdr_reflection_help":"Raue HDR-Reflexionen erfordern vollständig metallische Körper- und Flächenoberflächen. Die Prüfung meldet nicht unterstützte Kombinationen.",
"render.cpu_indirect_method_required":"Wähle CPU-Raytracing, um diesen Modus für indirekte Beleuchtung einzuschalten.",
"render.cpu_key_rim":"Haupt- und Konturlicht",
"render.cpu_last_check":"Letzte CPU-Prüfung",
"render.cpu_light_help":"Bis zu acht Richtungslichter. Eine leere eigene Liste schaltet sie aus; die Wahl einer Vorgabe stellt deren Lichter wieder her. Farben sind lineares RGB, und die Intensität ist ein relativer Wert, keine Leistungsangabe in Watt.",
"render.cpu_maps_saved_help":"CPU-Renderings können gespeicherte Karten undurchsichtiger Flächen verwenden: Oberfläche wählen und die Karten in der Aufnahme speichern. Mit indirekter Beleuchtung darf ein anderer Körper Glas verwenden; der Körper mit Karten darf kein Glas sein. Ausgewählte Reflexionen, Anisotropie und prozedurale Effekte ausschalten. Temporäre Kartendateipfade verwenden die GPU.",
"render.cpu_material_certification":"Die CPU-Prüfung validiert unterstützte Glasgeometrie vor dem Rendern. Geschlossenes Glas verwendet geometrische Abstände.",
"render.cpu_neutral":"Neutral",
"render.cpu_opaque_hint":"CPU-Raytracing verwendet einen undurchsichtigen Hintergrund.",
"render.cpu_opaque_required":"CPU-Raytracing benötigt einen undurchsichtigen Hintergrund. Entfernen Sie das Häkchen bei „Transparenter Hintergrund“, um fortzufahren.",
"render.cpu_options_pending":"Dieses CPU-Bedienfeld kann Emission, Oberflächeneffekte, Glas oder Anisotropie noch nicht bearbeiten. Entfernen Sie diese API-Optionen, bevor Sie es verwenden.",
"render.cpu_path_depth":"Pfadtiefe",
"render.cpu_path_help":"Berechnet Licht, das zwischen undurchsichtigen Flächen und durch unterstützte Glasformen läuft. Erfordert progressives Rendern und Raytracing-Schatten. Die Pfadtiefe zählt beleuchtete Flächen, Glasgrenzen und Streuereignisse ab dem ersten Treffer (1–16). Tiefe 1 liefert direkte Beleuchtung auf undurchsichtigen Flächen; höhere Werte erlauben mehr Reflexionen oder Glasdurchgänge und können länger dauern. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
"render.cpu_path_optics_reason":"Die Einstellungen bleiben erhalten. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
"render.cpu_point":"Punktlicht",
"render.cpu_point_add":"Punktlicht hinzufügen",
"render.cpu_point_help":"Bis zu acht Punktlichter, zusätzlich zur gewählten Richtungsbeleuchtung. Abstände in mm; der Mindestabstand darf den Referenzabstand nicht überschreiten.",
"render.cpu_primary_samples":"Kanten-Samples pro Pixel",
"render.cpu_progressive":"CPU progressiv",
"render.cpu_progressive_help":"Den vorhandenen CPU-Integrator in Durchgängen mit je einem Primär-Sample akkumulieren. Ausgewählte Reflexionen erfordern ein Sample; Flächenlichter, Dielektrika und Anisotropie erfordern „Einzelbild“.",
"render.cpu_progressive_adopt_help":"Die progressive Abtastung verwendet ein Kamerasample pro Durchlauf. Bildgröße, Lichtquellen und Reflexionseinstellungen bleiben unverändert.",
"render.cpu_progressive_always":"Progressive Abtastung ist immer aktiv",
"render.cpu_progressive_help":"Neue CPU-Kamerasamples sammeln und jeden vollständigen Durchgang zeigen. Ausgewählte Reflexionen verwenden einen Versuch pro Kamerasample; zertifiziertes Glas und planare Anisotropie behalten ihre bisherigen Grenzen. Adaptive Pilot- und Produktionssamples teilen sich das CPU-Profilbudget.",
"render.cpu_progressive_limits":"Höchstens 1.048.576 Pixel und 16.777.216 Auswertungen von Pixel × Sample. Das Ändern der Samples passt die Bildgröße nie automatisch an.",
"render.cpu_progressive_reflection_adopt":"Setzen Sie die Reflexionsversuche ausgewählter Körper auf 1, bevor Sie die progressive Abtastung übernehmen.",
"render.cpu_progressive_seed":"Startwert für das CPU-Sampling",
"render.cpu_progressive_spp":"Samples pro Pixel",
"render.cpu_quality_budget_help":"Ändert Größe, Obergrenze der Produktionssamples, Startwert und CPU-Ressourcenprofil. Kamera, Objektiv, Beleuchtung, GI-Tiefe und Effekte bleiben erhalten.",
"render.cpu_quality_progressive_required":"Aktiviere unten progressive Samples, bevor du ein CPU-Budget auswählst.",
"render.cpu_reflection_empty":"Keine Körper ausgewählt: Szenenreflexionen sind aus.",
@@ -3059,7 +3184,21 @@
"render.cpu_runtime_help":"Numerische Licht- und Umgebungseinstellungen werden mit dem Setup gespeichert. Körperreflexionen und HDR-Dateien bleiben vorübergehend. Vorhandene Werte für Flächenlicht und Spotkegel bleiben erhalten.",
"render.cpu_sampling_mode":"CPU-Sampling-Modus",
"render.cpu_shadows":"Schatten werfen",
"render.cpu_sky_bounds":"Die Himmelsrotation muss endlich innerhalb ±1.000.000 Radiant liegen; die Intensität muss 0–100 sein.",
"render.cpu_studio_enable":"Indirekte Beleuchtung für dieses Lichtset aktivieren",
"render.cpu_studio_enable_apply":"Indirekte Beleuchtung aktivieren und anwenden",
"render.cpu_studio_indirect_help":"Dieses Sonnen-Lichtset benötigt indirekte Beleuchtung mit Tiefe 8, verfolgte Schatten und eine Kamerastichprobe pro Durchlauf. Ist progressives Rendern ausgeschaltet, beginnt es mit 16 Stichproben pro Pixel und Startwert 0. Vorhandene progressive Einstellungen und das Ressourcenprofil bleiben erhalten. Unvereinbare Einstellungen verhindern die Anwendung, ohne sie zu ändern.",
"render.cpu_studio_radiance_limit":"Dieses Lichtset hat eine Flächenstrahldichte von {radiance}, über der CPU-Grenze von 64. Die vorgegebene Beleuchtung bleibt erhalten; wählen Sie ein anderes Lichtset oder GPU.",
"render.cpu_subsurface_denoise_reason":"Streuung im Glas auf der CPU benötigt ausgeschaltete Rauschreduzierung. Die Einstellungen bleiben erhalten.",
"render.cpu_subsurface_help":"Streuung lenkt Licht im Inneren geschlossenen Glases um. Positive Raten benötigen indirekte Beleuchtung ohne Rauschreduzierung. Die Rate zählt Kollisionen pro mm; 0 fügt keine Streuung hinzu. Positive Richtungswerte bevorzugen vorwärts, negative rückwärts.",
"render.cpu_summary":"CPU-Raytracing · Reflexionen · HDR · Richtungs- und Punktlichter",
"render.cpu_transparent_help":"Transparentes PNG entfernt den Hintergrund und erhält die Kantenabdeckung. Körper- und Flächendeckkraft müssen 100 % bleiben.",
"render.cpu_volume_help":"CPU-Atmosphäre nutzt indirekte Beleuchtung. Bei Extinktion null bleibt die gespeicherte Box ohne Nebel erhalten.",
"render.cpu_volume_method_adopt":"CPU-Raytracing mit indirekter Beleuchtung",
"render.cpu_volume_method_incompatible":"Diese Licht-, Sampling- oder Atmosphäreinstellungen lassen sich nicht auf diese Methode übertragen. Die Einstellungen bleiben erhalten.",
"render.cpu_volume_path_required":"Indirekte Beleuchtung einschalten, um CPU-Atmosphäre zu nutzen.",
"render.diag_preflight_details":"Details der Vorabprüfung",
"render.diag_request":"Aktuelle Anfrage im Wortlaut",
"render.diag_triangles":"Dreiecke",
"render.directional_sun_limit":"Die GPU unterstützt insgesamt acht gerichtete Lichtquellen, darunter höchstens eine weiche Sonne. Dupliziere ein hartes gerichtetes Licht für eine weitere entfernte Quelle.",
"render.discard_continue":"Entwurf verwerfen und fortfahren",
"render.draft_confirm_help":"Der aktuelle Render-Entwurf enthält nicht angewendete Änderungen. Speichern Sie sie vor dem Wechsel, verwerfen Sie sie ausdrücklich oder bearbeiten Sie weiter.",
"render.exr_pending":"Die GPU kann rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
"render.exr_pending":"CPU-Raytracing und GPU-Pathtracing können rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
"render.glass_model_help":"Glasrauheit streut durchgelassene Strahlen. Dispersion und Schichtfarbe verwenden drei Wellenlängenbänder. Raue Scheiben benötigen ausgeschaltete gerade Schattenübertragung.",
"render.glass_roughness":"Glasrauheit",
"render.gpu_depth":"Maximale Pfadtiefe",
"render.gpu_job_shader_wait":"Shader-Vorbereitung kann Minuten dauern",
"render.gpu_options_help":"Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
"render.gpu_path_crashed":"SolidCraft wurde beim letzten Einrichten auf diesem Grafikadapter beendet, wahrscheinlich im Grafiktreiber, und richtet ihn deshalb nicht von selbst erneut ein. Verwenden Sie stattdessen Raytracing auf der CPU oder versuchen Sie es erneut (nach einem Treiberupdate wird es ebenfalls erneut versucht).",
"render.gpu_path_failed":"GPU-Pathtracer nicht verfügbar",
"render.gpu_path_first_setup":"Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
"render.gpu_path_not_started":"Die Einrichtung des GPU-Pfadtracers wurde noch nicht gestartet.",
"render.gpu_path_preparing":"GPU-Pathtracer: Shader werden kompiliert…",
"render.gpu_path_retry":"Erneut versuchen",
"render.gpu_seed":"Zufallsstartwert",
@@ -3456,6 +3606,8 @@
"render.light_product_centre":"Produktmitte",
"render.light_size":"Größe",
"render.light_softness":"Weichheit",
"render.light_space_world":"Lichter in Weltkoordinaten",
"render.light_space_world_help":"Lichtpositionen und -richtungen verwenden Weltkoordinaten. Beim Umkreisen mit der Kamera bleiben die Lichter an ihrer Position.",
"render.light_temperature":"Farbtemperatur",
"render.light_temperature_help":"Angenäherte Schwarzkörperfarbe; als lineares RGB gespeichert.",
"render.light_type":"Typ",
@@ -3512,6 +3664,25 @@
"render.linear_help":"Behält ein lineares 32-Bit-Gleitkomma-RGBA-Rohbild vor Entrauschen, Belichtung, Tone-Mapping, Filtern und Hintergrund. Lineares sRGB/D65; Abdeckung vormultipliziert. Zusätzlicher Speicher wird ausdrücklich zugelassen. Keine Komprimierung und keine zusätzlichen Durchgänge.",
"render.linear_unavailable":"Für das gewählte fertige Bild wurde kein EXR behalten. Aktivieren Sie das Behalten vor dem Rendern; ein älteres PNG kann die rohe Strahldichte nicht wiederherstellen.",
"render.live.quality_help":"Interaktive Qualität: höchstens 512 Pixel an der längsten Kante. Verwendet den gewählten Renderer und seine unterstützten Effekte. Endauflösung, Einstellungen und Export bleiben getrennt.",
"render.live.refused":"Live nicht verfügbar: {reason}",
"render.live.source_outdated_help":"Zeigt das zuletzt aufgenommene Bild, während das Live-Rendering aktualisiert wird oder nicht verfügbar ist. Es kann von der aktuellen Szene oder den Bildeinstellungen abweichen.",
"render.live.source_settings_help":"Ausgabe öffnen, um Renderer und Bildeinstellungen zu bearbeiten.",
"render.material_coat_help":"Illustrative dielektrische Beschichtung über dem vorhandenen undurchsichtigen Erscheinungsbild. Gewicht null lässt das Grundmaterial unverändert.",
"render.material_cpu_glass_only":"Temporäre Bilddatei-Karten benötigen die GPU. Die CPU kann gespeicherte integrierte Oberflächenkarten verwenden.",
"render.material_cpu_optics_limits":"Indirekte Beleuchtung für unterstützte geschlossene Glasformen, geprüfte Verschachtelung und Streuung im Inneren einschalten. Dünne Scheiben können Licht für Schatten durchlassen. Sonst eine ebene Scheibe oder achsenparallele Box verwenden. Berührende Formen und durch geschlossenes Glas übertragene Schatten werden weiterhin nicht unterstützt. Die Werte bleiben erhalten.",
"render.material_cpu_saved_runtime_conflict":"Gespeicherte CPU-Optik kann nicht mit temporären optischen Bindungen kombiniert werden. Geprüfte Optik speichern oder die temporäre Bindung entfernen.",
"render.material_revalidate_needed":"Diese Zuweisung gehört zu einer früheren Szenenrevision. Prüfen Sie die genaue Körperidentität erneut, bevor Sie rendern.",
"render.material_roughness":"Rauheit des Klarlacks (0,045–1)",
"render.material_saved_edit_pending":"Diese Optikänderungen sind ungespeichert. Geprüfte Werte speichern, die gespeicherte Zuordnung entfernen oder Änderungen verwerfen. Temporäre Bindungen und nicht unterstützte Effekte können das gespeicherte Ziel nicht überschreiben.",
"render.material_target_missing":"Ordnungsnummer und Name des Körpers stimmen nicht mehr überein. Entfernen Sie diese Zuweisung und wählen Sie den gewünschten Körper ausdrücklich erneut.",
"render.material_thickness":"Dicke der dünnen Schicht (mm)",
@@ -3581,7 +3757,7 @@
"render.method_cpu":"CPU-Raytracing",
"render.method_gpu":"GPU-Pathtracing",
"render.method_limit_raster":"Die Rasterung verwendet die feste undurchsichtige Studiovorschau. Wählen Sie ausdrücklich eine Raytracing-Methode für die unterstützten optischen Einstellungen.",
"render.nested_help":"Bis zu 16 umschließende Schichten; jedes übergeordnete Element muss konvex und jedes untergeordnete strikt darin enthalten sein. Überschneidungen, sich berührende Flächen, dünne Schichten und die Transmission gerader Schattenstrahlen werden abgelehnt. Der Zustand „Kamera im Inneren“ folgt der zertifizierten Hierarchie. Erzeugt keine Flüssigkeitsoberfläche und verschmilzt keine sich berührenden CAD-Grenzflächen.",
@@ -3609,16 +3785,19 @@
"render.output_exr_unavailable":"EXR-Ausgabeeinstellungen sind hier nicht verfügbar. Linearer EXR-Export erfordert ein fertiges GPU-Bild mit gespeichertem linearem Original.",
"render.output_fit":"Einpassen",
"render.output_format":"Format",
"render.output_format_status":"PNG enthält das exakte fertige Anzeigebild. Optionales GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Filtern. Export ersetzt nie durch Vorschaucache oder anderen Auftrag.",
"render.output_format_status":"PNG enthält das exakte fertige Anzeigebild. Optionales CPU/GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Anzeigefiltern. Der Export verwendet nie den Vorschaucache oder einen anderen Auftrag.",
"render.output_master_unavailable":"Zum EXR-Export ein fertiges Bild mit EXR-Rohdaten auswählen. EXR-Rohdaten vor dem Rendern neuer Bilder aktivieren.",
"render.output_over_budget":"Das angeforderte Bild überschreitet den gewählten Ressourcenrahmen. Wählen Sie ausdrücklich eine passende Größe oder ein passendes Profil; es erfolgt keine automatische Herabstufung.",
"render.output_presentation_limits":"CPU-Bildeffekte und EXR-Ausgabe unterstützen Bilder mit bis zu 8192 Pixeln Breite oder Höhe und insgesamt höchstens 16.777.216 Pixeln.",
"render.output_presets_help":"Format, Qualität und Prüfliste befinden sich im Ausgabeeditor.",
"render.output_quality":"Qualität",
"render.output_quality_waiting":"Qualitätsdefinition steht noch aus",
"render.output_ratio_refused":"Die verknüpfte Abmessung überschreitet das Größenlimit dieser Methode. Die vorherige Größe bleibt erhalten; entsperren Sie das Verhältnis oder wählen Sie einen kleineren Wert.",
"render.output_raytrace_only":"Bildeffekte erfordern CPU-Raytracing oder GPU-Pathtracing.",
"render.output_ready":"Bereit zum Rendern",
"render.output_run_check":"Prüfung starten",
"render.output_size_limit":"{method}: bis zu {dimension} px pro Seite und {pixels} Pixel. Größte passende Größe: {width} × {height}.",
"render.preparation_checking":"Rendering wird geprüft",
"render.preparation_retirement_failed":"Die Bereinigung der Rendering-Vorbereitung ist fehlgeschlagen. Dieses Fenster kann kein weiteres Bild vorbereiten.",
"render.preparation_retiring":"Rendering-Vorbereitung wird abgeschlossen",
"render.preparation_unavailable":"Rendering-Vorbereitung nicht verfügbar",
"render.presentation_banner":"Bildeffekte funktionieren mit CPU- und GPU-Raytracing. Das Öffnen dieser Seite ändert die Methode nicht.",
"render.preview":"Integrierte Vorschau",
"render.preview_help":"Rasterdarstellung mit gemischter Transparenz. Brechung, HDRI und finales Rendering sind noch nicht verfügbar.",
"render.preview_image_settings_help":"Die Live-Vorschau behält eine feste Studiobeleuchtung. Belichtung und Bildbeleuchtung gelten für den Bildauftrag, wenn Sie „Prüfen“ oder „Start“ verwenden.",
@@ -3691,11 +3876,15 @@
"render.quality_ack":"Die Projektion mit festem Budget und ihre Auslassungen bestätigen",
"render.quality_projection_help":"Q-Vorgaben enthalten künftige Anforderungen. Diese Aktion projiziert nur ein unterstütztes PNG-Budget mit festen Samples und lässt adaptives Stoppen, rohe lineare Ausgabe und unabhängige Transmissionslimits ausdrücklich aus.",
"render.quality_projection_help":"Diese Prüfung ändert nur Auflösung, Abtastbudget, Pfadtiefe, Startwert und Rauschreduzierung. Andere Einstellungen bleiben erhalten; das Budget garantiert keine Konvergenz.",
"render.queue_pending":"In dieser Oberfläche gibt es keine automatische Produktionswarteschlange und keinen Animations-Renderer. Fertige Einzelbilder bleiben separat exportierbar.",
"render.result_provisional_help":"Dieser Zwischenstand ist nicht exportierbar. Der Export behält das letzte fertige Ergebnis; ein Abbruch kehrt zu diesem Ergebnis zurück.",
"render.result_reduced_display":"Anzeigevorschau auf die Geräte-/Cachegrenze reduziert. PNG und Export behalten die volle Auflösung; native 100%-Ansicht ist hier nicht verfügbar.",
"render.result_retained":"Das zuletzt fertiggestellte Bild wird angezeigt; ein neueres Rendering hat es noch nicht ersetzt.",
@@ -3814,9 +4003,11 @@
"render.shot_frame_subject":"Motiv einpassen",
"render.shot_invalid_camera":"Kamerawerte müssen endlich und im gültigen Bereich sein.",
"render.shot_lens":"Objektiv",
"render.shot_lens_cpu_progressive_reason":"Vor der CPU-Schärfentiefe unter Sampling die progressive Abtastung übernehmen.",
"render.shot_lens_enable_reason":"Schärfentiefe aktivieren, um Blende und Fokusabstand zu bearbeiten.",
"render.spot_gpu_help":"Die Winkel werden von der Abstrahlrichtung aus gemessen. Zwischen innerem und äußerem Winkel wird das Licht schwächer. Sehr schmale Übergänge erfordern CPU-Rendering.",
"render.spot_help":"Die Winkel werden von der Abstrahlrichtung aus gemessen. Zwischen innerem und äußerem Winkel wird das Licht schwächer.",
"render.spot_inner":"Innerer Halbwinkel",
"render.spot_outer":"Äußerer Halbwinkel",
"render.stage_frame":"Bildausschnitt",
"render.stage_layout.appearance_note":"Zum Anwenden auf die Auswahl klicken.",
"render.stage_layout.studio_note":"Vor dem Anwenden zur Prüfung klicken.",
@@ -3876,6 +4072,7 @@
"render.stage_panels.extent":"Ausdehnung",
"render.stage_panels.fit_save":"Einpassen und speichern",
"render.stage_panels.quality_ack":"Ich bestätige das feste Budget und seine Auslassungen",
"render.stage_panels.quality_omissions":"Adaptives Stoppen und Fehlerziele, separate Transmissionstiefe, Russian-Roulette-Regeln, lineare Rohausgabe, EXR-Master und Konvergenznachweis.",
"render.stage_panels.quality_omissions":"Dieses Budget legt keine adaptiven Fehlerziele, eigenen Transmissionsgrenzen, Russian-Roulette-Regeln oder Speicherung des linearen Masters fest und garantiert keine Konvergenz.",
"render.stage_panels.subtitle_quality":"GPU-Abtastbudget · nur aufgeführte Einstellungen",
"render.stage_panels.subtitle_rig":"Erstellter Lichtaufbau · Lichtquellen und passive Flächen",
"render.stage_panels.subtitle_saved_maps":"Erstellte Texturen · mit dieser Aufnahme gespeichert",
"render.stage_panels.subtitle_stage":"Physischer Boden und Bühne · an das Produkt angepasst",
@@ -3958,7 +4155,7 @@
"render.studio_area_help":"Bis zu vier einseitige Rechtecke. Orthogonale Halbkanten U und V bestimmen die Größe; U × V zeigt zur beleuchteten Seite. Die Koordinaten sind Weltmillimeter.",
"render.studio_center":"Mittelpunkt X, Y, Z (mm)",
"render.studio_denoise":"Rauschen im fertigen Bild reduzieren",
"render.studio_denoise_help":"Abschließender kantenerhaltender Filter; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
"render.studio_denoise_help":"Kantenerhaltender Filter für progressive und fertige Bilder; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
"render.studio_direction":"Richtung zur Sonne (X, Y, Z)",
"render.studio_environment":"Umgebung",
"render.studio_environment_bounds":"Die Umgebungsdrehung muss endlich und innerhalb von ±1.000.000 Radiant liegen, die Intensität zwischen 0 und 100.",
@@ -3994,6 +4191,19 @@
"render.subsurface_enable":"Random Walk für innere Streuung",
"render.subsurface_help":"Nur für geschlossene Dielektrika. Streuung und RGB-Absorption sind getrennte Koeffizienten pro mm. Pfade streuen innerhalb der tatsächlichen Geometrie und werden dann an der Grenzfläche gebrochen oder reflektiert. Deaktivieren Sie die Transmission gerader Schattenstrahlen, das Entrauschen von Oberflächen und das äußere Volumen; verwenden Sie eine Fresnel-Schicht. Kein Diffusions-Shader, kein Dichtegitter und kein gemessenes Material. Dichte Medien können mehr Samples und Pfadtiefe benötigen.",
"render.subsurface_rate":"Streukollisionsrate",
"render.surface_import.author":"Autor",
"render.surface_import.busy":"Bild wird importiert…",
"render.surface_import.complete":"Wählen Sie ein Bild und geben Sie Autor, Quelle und Lizenz an.",
"render.surface_import.save":"Bild am Körper speichern",
"render.surface_import.saved_cpu":"Speichern Sie Texturen für das CPU-Rendering.",
"render.surface_import.source":"Quelle",
"render.surface_import.title":"Bild importieren",
"render.tone_aces_fitted":"ACES angepasst",
"render.tone_reinhard":"Reinhard",
"render.transmission_scope":"GPU-Glas unterstützt zertifizierte getrennte Medien. Unbeschränkt verschachtelte Medien und ein rauschfreier Transmissionsschätzer werden nicht bereitgestellt.",
"render.volume_glass_required":"Atmosphäre und Glas können in dieser Einstellung nicht kombiniert werden.",
"render.volume_help":"GPU-Transport innerhalb einer endlichen Box, mit Beer-Lambert-Abschwächung und HG-Streuung auf Kamera- und indirekten Pfaden. Verwendet ausdrückliche Studio-, HDR- und Himmelsbeleuchtung einschließlich Schattenabschwächung. Ein positiver Phasenkosinus streut nach vorn. Glasgrenzflächen und Oberflächenentrauschen müssen ausdrücklich deaktiviert werden. Extinktion null lässt den Transport unverändert. Keine physikalische Mondatmosphäre, kein Dichtegitter, keine Volumenemission, kein spektraler Transport und keine zertifizierte Materialeigenschaft.",
"render.volume_lighting_required":"Für Atmosphäre eine Umgebung oder ein eigenes Lichtrig wählen.",
"ui.mcp_compact_note":"Für Agenten, die über den MCP-Server (solidcraft-cli mcp) verbunden sind: inspect_design fasst Skizzen zusammen und Werkzeugergebnisse werden nicht formatiert, sodass sie weniger Tokens verbrauchen. Gilt für Agentensitzungen, die nach der Änderung beginnen; --compact oder SOLIDCRAFT_MCP_COMPACT=1 schalten es ebenfalls ein.",
"ui.measure":"Messen",
"ui.mesh_format_3mf":"3MF",
"ui.mesh_format_3mf_tagged":"3MF mit Flächenmarken",
"ui.mesh_format_stl":"STL",
"ui.mesh_import_origin":"In den Ursprung verschieben",
"ui.mesh_import_origin_tip":"Das Netz in X und Y zentrieren und seine Unterseite auf Z = 0 legen. Wenn aus, behält das Netz die Koordinaten aus der Datei.",
"ui.part_size_needs_a_part":"Wählen Sie ein eingefügtes Standardbauteil (Schraube, Mutter, Scheibe oder Stift) aus, um seine Größe zu ändern. Einen Körper vergrößern oder verkleinern Sie mit Skalieren.",
"ui.project_face_edges_sketch":"Flächenkanten in neue Skizzen projizieren",
"ui.project_references_construction":"Referenzen als Konstruktionsgeometrie projizieren",
"ui.project_references_construction_note":"Projizieren, Schneiden, 3D-Geometrie einbeziehen und die Flächenkanten einer neuen Skizze auf einer Fläche erzeugen Konstruktionsgeometrie: Bezüge zum Zeichnen, die nicht zu Profilen gehören. Sie bleiben mit dem Modell verknüpft.",
"render.aging_raytrace_help":"Ageing is applied by CPU ray tracing and GPU path tracing. Raster keeps schematic rendering.",
"render.aging_refresh_targets":"Inspect current bodies",
"render.aging_roughness":"Layer roughness",
"render.aging_rust":"Oxidized iron / rust",
@@ -159,6 +160,7 @@
"render.background_workflow":"Choose the background you intend: a camera-only colour/gradient, a physical stage that receives light, or HDR illumination. They are independent. Live Preview is a modelling view; render a new image or regrade a retained master to see camera finishing.",
"render.base_color":"Base colour · sRGB",
"render.bloom_help":"Creative output-only glow from positive linear highlights, spread with three normalised box passes. Radius is in output pixels. Added in linear display light before sRGB; not a lens, volumetric effect or convergence improvement. The raw EXR is unchanged.",
"render.check_media_remaining":"Density grids, displacement, full spectral integration and propagated polarisation remain unavailable.",
"render.cpu_area_editor_help":"Full side vectors in millimeters; U cross V points towards the emitting side. This differs from GPU half-edge vectors.",
"render.cpu_light_help":"Up to eight directional lights. An empty custom list turns them off; selecting a preset restores its lights. Colours are linear RGB and intensity is relative, not watts.",
"render.glass_model_help":"Glass roughness scatters transmitted rays. Dispersion and film colour use three wavelength bands. Rough sheets require straight transmitted shadows off.",
"render.hdr_primaries_declared":"The source declares its colour primaries.",
"render.hdr_primaries_undeclared":"The source does not declare primaries; review the renderer colour requirements.",
@@ -235,6 +238,8 @@
"render.studio_area_help":"Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
"render.studio_center":"Centre X, Y, Z (mm)",
"render.studio_hdr_help":"Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
"render.surface_import.complete":"Choose an image and enter its author, source and licence.",
"material.editor.save_or_discard":"Tallenna tai hylkää materiaaliluonnos ennen materiaalin määrittämistä.",
"material.editor.saved":"Materiaali tallennettu. Kappaleiden määritykset eivät muuttuneet.",
"material.editor.scope":"Tallennus tallentaa tämän materiaalin määrittämättä sitä kappaleille. Tyhjät ominaisuudet ja olosuhteet jäävät tuntemattomiksi. Kortin tiheys ei muuta määritettyä tiheyttä tai massaa.",
@@ -4077,6 +4078,8 @@
"sample.plate.name":"Esimerkkilevy",
"sample.vents.about":"Urat suorakulmaisessa kuviossa: rivit, sarakkeet ja jako ovat parametreja",
"sample.vents.name":"Tuuletuslevy",
"scale.per_axis":"Akseleittain",
"scale.uniform":"Tasainen",
"search.command_keys":"Enter suorita Shift+Enter suorita ja pidä auki Esc sulje",
"file_load.reading_percent":"Lecture du fichier... {percent} %",
"file_load.rebuilding":"Reconstruction de l’entité {done} sur {total}",
"file_load.recovering":"Récupération de la conception",
"file_load.reloading_cloud":"Rechargement du nuage de points",
"file_load.stopping":"Attente de l’arrêt de l’opération précédente...",
"file_load.updating_links":"Mise à jour des liens",
"file_load.waiting":"En attente d’une opération sur fichier",
@@ -1624,7 +1630,7 @@
"help.sketch.insert_svg":"Lit les tracés d'un dessin SVG dans une esquisse, mis à l'échelle, tournés et placés comme vous le définissez. À utiliser pour transformer un logo ou un contour vectoriel en courbes que vous pouvez extruder.",
"help.sketch.inspect":"Ouvre un rapport sur l'esquisse ouverte: combien de degrés de liberté il reste et si elle est entièrement contrainte. À utiliser pour savoir ce qui bouge encore avant de terminer l'esquisse.",
"help.sketch.intersect":"Ajoute les courbes ou points où des corps, faces, arêtes ou d'autres esquisses traversent le plan d'esquisse. Ils restent liés et suivent le modèle lorsqu'il change.",
"help.sketch.intersection_curve":"Crée des courbes 3D là où deux corps ou faces se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
"help.sketch.intersection_curve":"Crée des courbes 3D là où deux corps, faces ou courbes d'autres esquisses se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
"help.sketch.isoparametric_curve":"Crée une courbe 3D le long de la direction U ou V d'une face, par un point que vous choisissez, liée à la face. À utiliser comme trajectoire ou ligne de partage qui suit la surface.",
"help.sketch.line":"Dessine des lignes connectées point par point, en chaîne ouverte ou en contour fermé. C'est l'outil de base de la plupart des profils.",
"help.sketch.line.midpoint":"Dessine une ligne de son milieu vers une extrémité, l'autre extrémité étant son symétrique. À utiliser pour des lignes centrées sur un point, comme une arête symétrique.",
@@ -1636,6 +1642,7 @@
"help.sketch.pattern.circular":"Copie la géométrie d'esquisse autour d'un centre, sur un cercle complet ou un angle donné. À utiliser pour des cercles de perçages, des rayons et des dents.",
"help.sketch.pattern.rectangular":"Copie la géométrie d'esquisse en lignes et colonnes à des espacements donnés. À utiliser pour des grilles de trous, de rainures ou d'aérations.",
"help.sketch.point":"Place un point d'esquisse. À utiliser comme référence pour des cotes, des contraintes, des centres de perçage ou des fonctions ultérieures.",
"help.sketch.point.divide":"Place des points qui divisent les courbes choisies en longueurs égales. Les points restent sur chaque courbe et gardent leur espacement quand elle change.",
"help.sketch.point3d":"Place un point dans l'espace, avec Esquisse 3D activée. À utiliser comme référence pour des courbes et trajectoires 3D.",
"help.sketch.polygon.circumscribed":"Dessine un polygone régulier autour d'un cercle, dimensionné du centre au milieu d'un côté. À utiliser lorsque la largeur sur plats est connue, comme pour un écrou hexagonal.",
"help.sketch.polygon.edge":"Dessine un polygone régulier à partir des deux extrémités d'un côté. À utiliser lorsque la longueur et la position d'un côté sont connues.",
"material.editor.save_or_discard":"Enregistrez ou abandonnez le brouillon de matériau avant d'affecter un matériau.",
"material.editor.saved":"Matériau enregistré. Les affectations des corps sont inchangées.",
"material.editor.scope":"Enregistrer stocke ce matériau sans l'affecter à des corps. Les propriétés et conditions vides restent inconnues. La masse volumique de la fiche ne modifie ni la masse volumique affectée ni la masse.",
@@ -2158,6 +2166,8 @@
"prefs.bottom_centre":"Bas centre",
"prefs.bottom_right":"Bas droite",
"prefs.browser":"Navigateur",
"prefs.browser_body_features":"Fonctions sous les corps",
"prefs.browser_body_features_note":"Chaque corps du navigateur se déplie sur les fonctions de l'historique qui l'ont construit, dans l'ordre de l'historique.",
"prefs.browser_components_open":"Composants dépliés au départ",
"prefs.browser_components_open_note":"Chaque composant du navigateur commence déplié, comme avant, au lieu de rester replié jusqu'à ce que vous le dépliiez.",
"prefs.browser_eye_left":"Œil à côté de la flèche de dépliage",
@@ -2201,6 +2211,8 @@
"prefs.edges":"Arêtes et ombrage",
"prefs.extend":"Étendre",
"prefs.extra_navigation":"Ctrl+glisser gauche dans le vide fait orbiter; maintenir Espace pour un panoramique.",
"prefs.extrude_arrow_face_stops":"La flèche d'extrusion s'arrête aux faces",
"prefs.extrude_arrow_face_stops_note":"En faisant glisser la flèche de distance d'Extrusion, elle s'arrête aux faces planes parallèles au profil, comme l'autre côté d'un bloc qu'on découpe, et souligne la face. Désactivé : la flèche avance seulement par pas ronds.",
"prefs.extrude_view_isometric":"Passer la vue en isométrique à l'ouverture de l'extrusion",
"prefs.extrude_view_return":"Revenir à la vue précédente à la fermeture de l'extrusion",
"prefs.follows_os":"Suit le système",
@@ -2280,6 +2292,8 @@
"prefs.mouse":"Souris et pavé tactile",
"prefs.mouse_heading":"Souris",
"prefs.mouse_mapping":"Affectation des boutons de la souris",
"prefs.move_drag_highlights":"Mettre en évidence ce qui est sous le pointeur pendant que les flèches de Déplacer font glisser un composant",
"prefs.move_drag_highlights_tip":"Désactivé : pendant que les flèches de Déplacer font glisser un composant, rien n’est mis en évidence sous le pointeur, ce qui garde les grands assemblages fluides. Avec une coupe ou une analyse affichée, la mise en évidence reste, et une pièce glissée le long de ses liaisons n’en montre jamais.",
"prefs.navigation":"Navigation",
"prefs.navigation_note":"Comment la souris, le pavé tactile et la souris 3D déplacent la vue.",
"prefs.navigator_fit":"Cadrer le modèle après un changement de vue du repère de navigation",
@@ -2355,6 +2369,8 @@
"prefs.shortcuts":"Raccourcis",
"prefs.single_surface_analysis":"Utiliser une seule analyse de surface",
"prefs.single_surface_analysis_help":"Désactivé, l'ombrage de surface et Continuité peuvent rester actifs ensemble. Activé, seule la dernière analyse lancée ou modifiée est conservée.",
"prefs.sketch.escape_twice_finishes":"Un double Échap termine l'esquisse",
"prefs.sketch.escape_twice_finishes_note":"Deux pressions rapides sur Échap terminent l'esquisse. Désactivé : Échap annule seulement l'outil ou la sélection, et Terminer l'esquisse ferme l'esquisse.",
"prefs.sketch_align_points":"S'aligner sur d'autres points d'esquisse pendant le dessin",
"prefs.sketch_align_points_note":"Pendant le dessin, un point s'aligne à la fois horizontalement sur un point d'esquisse et verticalement sur un autre, avec un guide en pointillés, et le clic conserve les deux alignements. Désactivé : uniquement sur le point précédent, un axe à la fois.",
"prefs.sketch_conflict_colour":"Géométrie en conflit dans la couleur de conflit",
"render.cpu_maps_saved_help":"Le rendu CPU accepte les cartes opaques enregistrées : choisissez une surface, puis enregistrez ses cartes dans la vue. Avec l’éclairage indirect, un autre corps peut utiliser Verre ; le corps avec les cartes ne le peut pas. Désactivez les réflexions sélectionnées, l’anisotropie et les effets procéduraux. Les fichiers temporaires de cartes utilisent le GPU.",
"render.cpu_material_certification":"La vérification CPU valide la géométrie de verre prise en charge avant le rendu. Le verre fermé utilise la distance géométrique.",
"render.cpu_neutral":"Neutre",
"render.cpu_opaque_required":"Le lancer de rayons CPU nécessite un arrière-plan opaque. Décochez Arrière-plan transparent pour continuer.",
"render.cpu_options_pending":"Ce panneau CPU ne peut pas encore modifier l'émission, les effets de surface, le verre ni l'anisotropie. Effacez ces options d'API avant de l'utiliser.",
@@ -3814,6 +3832,8 @@
"sample.plate.name":"Plaque d'exemple",
"sample.vents.about":"Rainures en répétition rectangulaire: lignes, colonnes et pas sont des paramètres",
"sample.vents.name":"Plaque d'aération",
"scale.per_axis":"Par axe",
"scale.uniform":"Uniforme",
"search.command_keys":"Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
"sketch.dimension_applied_renamed":"Cote appliquée; paramètre nommé `{name}`",
"sketch.divide_hint":"Les points divisent chaque courbe en longueurs égales et les gardent quand elle change.",
"sketch.dof_left":"{count} degrés de liberté restants",
"sketch.dof_left_one":"{count} degré de liberté restant",
"sketch.draw_curves":"Dessiner en {linetype}",
@@ -4443,6 +4464,7 @@
"ui.click_selects_whole_bodies":"Un clic sélectionne des corps entiers",
"ui.click_then_press_key":"Cliquer, puis appuyer sur la nouvelle touche",
"ui.close":"Fermer",
"ui.close_tab":"Fermer l’onglet",
"ui.coil":"Hélice",
"ui.coincident":"Coïncidence",
"ui.collapse":"Réduire",
@@ -4689,6 +4711,7 @@
"ui.heading_create_sketch":"CRÉER UNE ESQUISSE",
"ui.heading_cylinder":"CYLINDRE",
"ui.heading_design":"DESIGN",
"ui.heading_divide_curve":"DIVISER LA COURBE",
"ui.heading_draft":"DÉPOUILLE",
"ui.heading_edit":"ÉDITION",
"ui.heading_equation_curve":"COURBE PAR ÉQUATION",
@@ -4846,6 +4869,8 @@
"ui.mesh_format_3mf":"3MF",
"ui.mesh_format_3mf_tagged":"3MF avec étiquettes de faces",
"ui.mesh_format_stl":"STL",
"ui.mesh_import_origin":"Déplacer à l'origine",
"ui.mesh_import_origin_tip":"Centre le maillage en X et Y et pose sa base sur Z = 0. Désactivé, le maillage garde les coordonnées enregistrées dans le fichier.",
"ui.minor_radius":"Petit rayon",
"ui.mirror":"Symétrie",
"ui.mirror_line":"Ligne de symétrie",
@@ -4926,6 +4951,7 @@
"ui.point_cloud_add_filter":"Ajouter un filtre…",
"ui.point_cloud_auto_range":"Plage automatique",
"ui.point_cloud_color_by":"Colorer par",
"ui.point_cloud_dots":"Points isolés",
"ui.point_cloud_file_colors":"Couleurs du fichier",
"ui.point_cloud_filter_by":"Filtrer par",
"ui.point_cloud_height_high":"Plus haut",
@@ -4934,6 +4960,7 @@
"ui.point_cloud_no_file_colors":"Le fichier n'a pas de couleurs",
"ui.point_cloud_pause":"Pause",
"ui.point_cloud_play":"Lire",
"ui.point_cloud_reload":"Recharger le fichier",
"ui.point_cloud_single_color":"Couleur unique",
"ui.point_cloud_size":"Taille des points",
"ui.point_cloud_units":"Unités du fichier",
@@ -4993,6 +5020,8 @@
"ui.renderer":"Moteur de rendu",
"ui.repeat":"Répéter",
"ui.repeat_nothing":"Rien à répéter pour l'instant: lancer d'abord un outil ou une boîte de dialogue.",
"ui.replace_sketch.from_file":"Depuis un fichier de conception…",
"ui.replace_sketch.no_sketches":"Cette conception ne contient aucune esquisse",
"ui.replace_with_link_done":"Les copies sont maintenant des placements de la conception liée.",
"ui.report_a_bug":"Signaler un bug",
"ui.reset":"Réinitialiser",
@@ -5026,6 +5055,7 @@
"ui.second_rail":"Second rail",
"ui.section_analysis":"Analyse de coupe",
"ui.section_size":"Taille de la section",
"ui.segments":"Segments",
"ui.select":"Sélectionner",
"ui.selectOther":"Sélectionner un autre…",
"ui.selectTangentChain":"Sélectionner la chaîne tangente",
"help.sketch.insert_svg":"Le os trazados dun debuxo SVG nun bosquexo, escalados, xirados e colocados onde indiques. Úsao para converter un logotipo ou un contorno vectorial en curvas que podes extruír.",
"help.sketch.inspect":"Abre un informe do bosquexo aberto: cantos graos de liberdade quedan e se está totalmente restrinxido. Úsao para saber o que aínda se move antes de rematar o bosquexo.",
"help.sketch.intersect":"Engade as curvas ou os puntos onde corpos, caras, arestas ou outros bosquexos cruzan o plano do bosquexo. Quedan ligados e seguen o modelo cando cambia.",
"help.sketch.intersection_curve":"Crea curvas 3D onde se atopan dous corpos ou caras, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
"help.sketch.intersection_curve":"Crea curvas 3D onde se atopan dous corpos, caras ou curvas doutros bosquexos, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
"help.sketch.isoparametric_curve":"Crea unha curva 3D na dirección U ou V dunha cara por un punto que escollas, ligada á cara. Úsaa como traxectoria ou liña de división que segue a superficie.",
"help.sketch.line":"Debuxa liñas conectadas punto a punto, como cadea aberta ou como contorno pechado. É a ferramenta básica para a maioría dos perfís.",
"help.sketch.line.midpoint":"Debuxa unha liña desde o seu punto medio ata un extremo, co outro extremo reflectido. Úsao para liñas centradas nun punto, como unha aresta simétrica.",
"material.editor.save_or_discard":"Garda ou descarta o borrador do material antes de asignar un material.",
"material.editor.saved":"Material gardado. As asignacións aos corpos non cambian.",
"material.editor.scope":"Gardar almacena este material sen asignalo a corpos. As propiedades e condicións en branco seguen sendo descoñecidas. A densidade da ficha non cambia a densidade asignada nin a masa.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Placa de exemplo",
"sample.vents.about":"Ranuras nun patrón rectangular: filas, columnas e paso son parámetros",
"sample.vents.name":"Placa de ventilación",
"scale.per_axis":"Por eixo",
"scale.uniform":"Uniforme",
"search.command_keys":"Intro executar Maiús+Intro executar e manter aberto Esc pechar",
"help.sketch.insert_svg":"Čita putanje SVG crteža u skicu, skalirane, zakrenute i postavljene kako zadate. Upotrijebite ga za pretvaranje logotipa ili vektorskog obrisa u krivulje koje možete ekstrudirati.",
"help.sketch.inspect":"Otvara izvješće o otvorenoj skici: koliko je stupnjeva slobode preostalo i je li potpuno ograničena. Upotrijebite ga kako biste saznali što se još pomiče prije dovršetka skice.",
"help.sketch.intersect":"Dodaje krivulje ili točke u kojima tijela, lica, bridovi ili druge skice presijecaju ravninu skice. Ostaju povezane i prate model kad se promijeni.",
"help.sketch.intersection_curve":"Izrađuje 3D krivulje na mjestu gdje se dva tijela ili lica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
"help.sketch.intersection_curve":"Izrađuje 3D krivulje na mjestu gdje se dva tijela, lica ili krivulje drugih skica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
"help.sketch.isoparametric_curve":"Izrađuje 3D krivulju u smjeru U ili V lica kroz točku koju odaberete, povezanu s licem. Upotrijebite je kao putanju ili liniju razdvajanja koja prati plohu.",
"help.sketch.line":"Crta povezane linije točku po točku, kao otvoreni lanac ili zatvoreni obris. To je osnovni alat za većinu profila.",
"help.sketch.line.midpoint":"Crta liniju od njezine središnje točke do jednog kraja, a drugi kraj zrcali. Upotrijebite ga za linije centrirane na točku, primjerice simetričan brid.",
"material.editor.save_or_discard":"Spremite ili odbacite nacrt materijala prije dodjele materijala.",
"material.editor.saved":"Materijal je spremljen. Dodjele tijelima ostaju nepromijenjene.",
"material.editor.scope":"Spremi pohranjuje ovaj materijal bez dodjele tijelima. Prazna svojstva i uvjeti ostaju nepoznati. Gustoća s kartice ne mijenja dodijeljenu gustoću ni masu.",
"help.sketch.insert_svg":"Beolvassa egy SVG-rajz útvonalait egy vázlatba, az Ön által megadott léptékkel, elforgatással és helyen. Használja egy logó vagy vektoros körvonal kihúzható görbékké alakítására.",
"help.sketch.inspect":"Jelentést nyit a megnyitott vázlatról: hány szabadságfok maradt, és teljesen kényszerezett-e. Használja annak kiderítésére, mi mozdul még, mielőtt befejezi a vázlatot.",
"help.sketch.intersect":"Hozzáadja azokat a görbéket vagy pontokat, ahol testek, lapok, élek vagy más vázlatok metszik a vázlatsíkot. Kapcsoltak maradnak, és követik a modellt, ha az megváltozik.",
"help.sketch.intersection_curve":"3D görbéket hoz létre ott, ahol két test vagy lap találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
"help.sketch.intersection_curve":"3D görbéket hoz létre ott, ahol két test, lap vagy más vázlatok görbéi találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
"help.sketch.isoparametric_curve":"3D görbét hoz létre egy lap U vagy V iránya mentén egy kiválasztott ponton át, a laphoz kapcsolva. Használja útvonalként vagy osztóvonalként, amely követi a felületet.",
"help.sketch.line":"Összefüggő vonalakat rajzol pontról pontra, nyitott láncként vagy zárt körvonalként. Ez a legtöbb profil alapeszköze.",
"help.sketch.line.midpoint":"Vonalat rajzol a felezőpontjától az egyik végéig, a másik véget tükrözve. Használja egy pontra középen álló vonalakhoz, például szimmetrikus élhez.",
"material.editor.save_or_discard":"Anyag hozzárendelése előtt mentse vagy vesse el az anyagvázlatot.",
"material.editor.saved":"Az anyag mentve. A testekhez rendelt anyagok nem változtak.",
"material.editor.scope":"A Mentés az anyagot a testekhez való hozzárendelés nélkül tárolja. Az üres tulajdonságok és feltételek ismeretlenek maradnak. A kártyán szereplő sűrűség nem változtatja meg a hozzárendelt sűrűséget vagy a tömeget.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Mintalemez",
"sample.vents.about":"Hornyok téglalap mintában: a sorok, oszlopok és osztás paraméterek",
"sample.vents.name":"Szellőzőlemez",
"scale.per_axis":"Tengelyenként",
"scale.uniform":"Egyenletes",
"search.command_keys":"Enter futtatás Shift+Enter futtatás és nyitva tartás Esc bezárás",
"help.sketch.insert_svg":"Membaca path gambar SVG ke dalam sketsa, diskalakan, diputar, dan ditempatkan sesuai pengaturan Anda. Gunakan untuk mengubah logo atau kontur vektor menjadi kurva yang dapat diekstrusi.",
"help.sketch.inspect":"Membuka laporan tentang sketsa yang terbuka: berapa derajat kebebasan yang tersisa dan apakah sketsa terbatasi penuh. Gunakan untuk mengetahui apa yang masih bergerak sebelum Anda menyelesaikan sketsa.",
"help.sketch.intersect":"Menambahkan kurva atau titik tempat bodi, muka, tepi, atau sketsa lain memotong bidang sketsa. Semuanya tetap tertaut dan mengikuti model saat model berubah.",
"help.sketch.intersection_curve":"Membuat kurva 3D di tempat dua bodi atau muka bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
"help.sketch.intersection_curve":"Membuat kurva 3D di tempat dua bodi, muka, atau kurva sketsa lain bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
"help.sketch.isoparametric_curve":"Membuat kurva 3D sepanjang arah U atau V sebuah muka melalui titik yang Anda pilih, tertaut ke muka itu. Gunakan sebagai jalur atau garis pisah yang mengikuti permukaan.",
"help.sketch.line":"Menggambar garis-garis tersambung titik demi titik, sebagai rantai terbuka atau kontur tertutup. Ini alat dasar untuk sebagian besar profil.",
"help.sketch.line.midpoint":"Menggambar garis dari titik tengahnya ke satu ujung, dengan ujung lainnya dicerminkan. Gunakan untuk garis yang dipusatkan pada sebuah titik, seperti tepi simetris.",
@@ -1889,6 +1889,7 @@
"material.editor.preserved_appearance":"Tampilan yang dipertahankan",
"material.editor.save_or_discard":"Simpan atau buang draf material sebelum menetapkan material.",
"material.editor.saved":"Material disimpan. Penetapan ke bodi tidak berubah.",
"material.editor.scope":"Simpan menyimpan material ini tanpa menetapkannya ke bodi. Properti dan kondisi yang dikosongkan tetap dianggap tidak diketahui. Massa jenis pada kartu tidak mengubah massa jenis atau massa yang sudah ditetapkan.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Pelat Contoh",
"sample.vents.about":"Slot dalam pola persegi panjang: baris, kolom, dan pitch adalah parameter",
"sample.vents.name":"Pelat Ventilasi",
"scale.per_axis":"Per sumbu",
"scale.uniform":"Seragam",
"search.command_keys":"Enter jalankan Shift+Enter jalankan dan tetap buka Esc tutup",
"help.sketch.insert_svg":"Legge i tracciati di un disegno SVG in uno schizzo, scalati, ruotati e posizionati come imposti. Usalo per trasformare un logo o un contorno vettoriale in curve che puoi estrudere.",
"help.sketch.inspect":"Apre un rapporto sullo schizzo aperto: quanti gradi di libertà restano e se è completamente vincolato. Usalo per scoprire cosa si muove ancora prima di terminare lo schizzo.",
"help.sketch.intersect":"Aggiunge le curve o i punti in cui corpi, facce, spigoli o altri schizzi attraversano il piano dello schizzo. Restano collegati e seguono il modello quando cambia.",
"help.sketch.intersection_curve":"Crea curve 3D dove si incontrano due corpi o facce, collegate a essi. Usale come percorsi o guide che seguono il modello.",
"help.sketch.intersection_curve":"Crea curve 3D dove si incontrano due corpi, facce o curve di altri schizzi, collegate a essi. Usale come percorsi o guide che seguono il modello.",
"help.sketch.isoparametric_curve":"Crea una curva 3D lungo la direzione U o V di una faccia passante per un punto che scegli, collegata alla faccia. Usala come percorso o linea di divisione che segue la superficie.",
"help.sketch.line":"Disegna linee collegate punto per punto, come catena aperta o contorno chiuso. È lo strumento di base per la maggior parte dei profili.",
"help.sketch.line.midpoint":"Disegna una linea dal suo punto medio fino a un'estremità, con l'altra estremità speculare. Usalo per linee centrate su un punto, come un bordo simmetrico.",
@@ -1637,6 +1639,7 @@
"help.sketch.pattern.circular":"Copia la geometria dello schizzo attorno a un punto centrale, su un cerchio completo o su un angolo stabilito. Usalo per cerchi di bulloni, raggi e denti.",
"help.sketch.pattern.rectangular":"Copia la geometria dello schizzo in righe e colonne con spaziature stabilite. Usalo per griglie di fori, asole o prese d'aria.",
"help.sketch.point":"Colloca un punto dello schizzo. Usalo come riferimento per quote, vincoli, centri di fori o lavorazioni successive.",
"help.sketch.point.divide":"Posiziona punti che dividono le curve scelte in lunghezze uguali. I punti restano su ogni curva e ne mantengono la spaziatura quando cambia.",
"help.sketch.point3d":"Colloca un punto nello spazio, con Schizzo 3D attivo. Usalo come riferimento per curve e percorsi 3D.",
"help.sketch.polygon.circumscribed":"Disegna un poligono regolare attorno a un cerchio, dimensionato dal centro al punto medio di un lato. Usalo quando è nota l'apertura chiave, come per un dado esagonale.",
"help.sketch.polygon.edge":"Disegna un poligono regolare a partire dai due estremi di un lato. Usalo quando sono note la lunghezza e la posizione di un lato.",
"material.editor.save_or_discard":"Salva o scarta la bozza del materiale prima di assegnare un materiale.",
"material.editor.saved":"Materiale salvato. Le assegnazioni ai corpi non sono cambiate.",
"material.editor.scope":"Salva memorizza questo materiale senza assegnarlo ai corpi. Le proprietà e le condizioni lasciate vuote restano sconosciute. La densità della scheda non cambia la densità assegnata né la massa.",
@@ -3813,6 +3817,8 @@
"sample.plate.name":"Piastra di esempio",
"sample.vents.about":"Asole in ripetizione rettangolare: righe, colonne e passo sono parametri",
"sample.vents.name":"Piastra di ventilazione",
"scale.per_axis":"Per asse",
"scale.uniform":"Uniforme",
"search.command_keys":"Invio esegui Maiusc+Invio esegui e tieni aperto Esc chiudi",
"material.editor.save_or_discard":"მასალის მინიჭებამდე შეინახეთ ან გააუქმეთ მასალის მონახაზი.",
"material.editor.saved":"მასალა შენახულია. სხეულებზე მინიჭებები არ შეცვლილა.",
"material.editor.scope":"შენახვა ამ მასალას ინახავს სხეულებზე მინიჭების გარეშე. ცარიელი თვისებები და პირობები უცნობად რჩება. ბარათის სიმკვრივე არ ცვლის მინიჭებულ სიმკვრივეს ან მასას.",
"help.sketch.insert_svg":"Membaca laluan lukisan SVG ke dalam lakaran, diskalakan, diputar dan diletakkan mengikut tetapan anda. Gunakan untuk menukar logo atau garis luar vektor kepada lengkung yang boleh disemperit.",
"help.sketch.inspect":"Membuka laporan tentang lakaran yang terbuka: berapa banyak darjah kebebasan yang tinggal dan sama ada ia terkekang sepenuhnya. Gunakan untuk mengetahui apa yang masih bergerak sebelum anda menamatkan lakaran.",
"help.sketch.intersect":"Menambah lengkung atau titik tempat jasad, muka, tepi atau lakaran lain melintasi satah lakaran. Ia kekal terpaut dan mengikut model apabila model berubah.",
"help.sketch.intersection_curve":"Mencipta lengkung 3D tempat dua jasad atau muka bertemu, terpaut kepadanya. Gunakan sebagai laluan atau rel pemandu yang mengikut model.",
"help.sketch.intersection_curve":"Mencipta lengkung 3D tempat dua jasad, muka atau lengkung lakaran lain bertemu, terpaut kepadanya. Gunakan sebagai laluan atau rel pemandu yang mengikut model.",
"help.sketch.isoparametric_curve":"Mencipta lengkung 3D di sepanjang arah U atau V muka melalui titik yang anda pilih, terpaut kepada muka itu. Gunakan sebagai laluan atau garis belahan yang mengikut permukaan.",
"help.sketch.line":"Melukis garis bersambung titik demi titik, sebagai rantaian terbuka atau garis luar tertutup. Ia ialah alat asas bagi kebanyakan profil.",
"help.sketch.line.midpoint":"Melukis garis daripada titik tengahnya ke satu hujung, dengan hujung satu lagi dicerminkan. Gunakan untuk garis yang berpusat pada satu titik, seperti tepi simetri.",
"material.editor.save_or_discard":"Simpan atau buang draf bahan sebelum menetapkan bahan.",
"material.editor.saved":"Bahan disimpan. Penetapan jasad tidak berubah.",
"material.editor.scope":"Simpan menyimpan bahan ini tanpa menetapkannya pada jasad. Sifat dan keadaan yang dibiarkan kosong kekal tidak diketahui. Ketumpatan kad tidak mengubah ketumpatan yang ditetapkan atau jisim.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Plat Contoh",
"sample.vents.about":"Corak slot segi empat tepat: baris, lajur dan pic sebagai parameter",
"sample.vents.name":"Plat Pengudaraan",
"scale.per_axis":"Mengikut paksi",
"scale.uniform":"Seragam",
"search.command_keys":"Enter jalankan Shift+Enter jalankan dan kekalkan terbuka Esc tutup",
"help.sketch.insert_svg":"Leser banene fra en SVG-tegning inn i en skisse, skalert, dreid og plassert slik du angir. Bruk det til å gjøre en logo eller vektorkontur om til kurver du kan ekstrudere.",
"help.sketch.inspect":"Åpner en rapport om den åpne skissen: hvor mange frihetsgrader som gjenstår og om den er fullstendig bundet. Bruk det til å finne ut hva som fortsatt beveger seg før du fullfører skissen.",
"help.sketch.intersect":"Legger til kurvene eller punktene der kropper, flater, kanter eller andre skisser krysser skisseplanet. De forblir koblet og følger modellen når den endres.",
"help.sketch.intersection_curve":"Oppretter 3D-kurver der to kropper eller flater møtes, koblet til dem. Bruk dem som baner eller føringskurver som følger modellen.",
"help.sketch.intersection_curve":"Oppretter 3D-kurver der to kropper, flater eller kurver fra andre skisser møtes, koblet til dem. Bruk dem som baner eller føringskurver som følger modellen.",
"help.sketch.isoparametric_curve":"Oppretter en 3D-kurve langs U- eller V-retningen til en flate gjennom et punkt du velger, koblet til flaten. Bruk den som en bane eller delelinje som følger flaten.",
"help.sketch.line":"Tegner sammenhengende linjer punkt for punkt, som en åpen kjede eller en lukket kontur. Det er det grunnleggende verktøyet for de fleste profiler.",
"help.sketch.line.midpoint":"Tegner en linje fra midtpunktet og ut til den ene enden, med den andre enden speilet. Bruk det til linjer sentrert på et punkt, for eksempel en symmetrisk kant.",
"material.editor.save_or_discard":"Lagre eller forkast materialutkastet før du tildeler et materiale.",
"material.editor.saved":"Materialet er lagret. Kroppstildelinger er uendret.",
"material.editor.scope":"Ved lagring lagres materialet uten at det tildeles kropper. Tomme egenskaper og betingelser forblir ukjente. Tettheten på kortet endrer ikke tildelt tetthet eller masse.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Eksempelplate",
"sample.vents.about":"Spor i et rektangulært mønster: rader, kolonner og stigning er parametere",
"sample.vents.name":"Ventilasjonsplate",
"scale.per_axis":"Per akse",
"scale.uniform":"Jevn",
"search.command_keys":"Enter kjør Shift+Enter kjør og behold åpen Esc lukk",
"help.sketch.insert_svg":"Leest de paden van een SVG-tekening in een schets, geschaald, gedraaid en geplaatst waar je instelt. Gebruik dit om een logo of vectorcontour om te zetten in curven die je kunt extruderen.",
"help.sketch.inspect":"Opent een rapport over de geopende schets: hoeveel vrijheidsgraden er nog over zijn en of hij volledig beperkt is. Gebruik dit om te zien wat nog beweegt voordat je de schets voltooit.",
"help.sketch.intersect":"Voegt de curven of punten toe waar lichamen, vlakken, randen of andere schetsen het schetsvlak kruisen. Ze blijven gekoppeld en volgen het model wanneer dat verandert.",
"help.sketch.intersection_curve":"Maakt 3D-curven waar twee lichamen of vlakken samenkomen, eraan gekoppeld. Gebruik ze als paden of geleiders die het model volgen.",
"help.sketch.intersection_curve":"Maakt 3D-curven waar twee lichamen, vlakken of curven van andere schetsen samenkomen, eraan gekoppeld. Gebruik ze als paden of geleiders die het model volgen.",
"help.sketch.isoparametric_curve":"Maakt een 3D-curve langs de U- of V-richting van een vlak door een punt dat je kiest, gekoppeld aan het vlak. Gebruik dit als pad of splitslijn die het oppervlak volgt.",
"help.sketch.line":"Tekent verbonden lijnen punt voor punt, als open keten of gesloten contour. Het is het basisgereedschap voor de meeste profielen.",
"help.sketch.line.midpoint":"Tekent een lijn vanuit zijn middelpunt naar één uiteinde, waarbij het andere uiteinde wordt gespiegeld. Gebruik dit voor lijnen die op een punt zijn gecentreerd, zoals een symmetrische rand.",
"material.editor.save_or_discard":"Sla het materiaalconcept op of verwerp het voordat je een materiaal toewijst.",
"material.editor.saved":"Materiaal opgeslagen. Toewijzingen aan lichamen zijn ongewijzigd.",
"material.editor.scope":"Opslaan bewaart dit materiaal zonder het aan lichamen toe te wijzen. Lege eigenschappen en voorwaarden blijven onbekend. De dichtheid op de kaart wijzigt de toegewezen dichtheid of massa niet.",
@@ -3818,6 +3819,8 @@
"sample.plate.name":"Voorbeeldplaat",
"sample.vents.about":"Sleuven in een rechthoekig patroon: rijen, kolommen en spoed zijn parameters",
"sample.vents.name":"Ventilatieplaat",
"scale.per_axis":"Per as",
"scale.uniform":"Uniform",
"search.command_keys":"Enter uitvoeren Shift+Enter uitvoeren en open houden Esc sluiten",
"ai.prefs.instructions_note":"Sent to the agent at the start of every chat, after SolidCraft's own instructions: how you like to work, rules to keep to. A change applies from the next chat (Connect or New chat)."
},
"ar":{
"ai.prefs.instructions":"تعليمات مخصصة",
"ai.prefs.instructions_note":"تُرسَل إلى الوكيل في بداية كل محادثة، بعد تعليمات SolidCraft الخاصة: طريقتك المفضلة في العمل والقواعد التي يجب الالتزام بها. يسري أي تغيير من المحادثة التالية (اتصال أو محادثة جديدة)."
},
"he":{
"ai.prefs.instructions":"הוראות מותאמות אישית",
"ai.prefs.instructions_note":"נשלחות לסוכן בתחילת כל צ'אט, אחרי ההוראות של SolidCraft עצמה: דרך העבודה המועדפת עליך וכללים שיש להקפיד עליהם. שינוי חל מהצ'אט הבא (התחברות או צ'אט חדש)."
"ai.prefs.instructions_note":"Được gửi cho agent khi bắt đầu mỗi cuộc trò chuyện, sau hướng dẫn riêng của SolidCraft: cách bạn muốn làm việc, các quy tắc cần tuân theo. Thay đổi có hiệu lực từ cuộc trò chuyện tiếp theo (Kết nối hoặc Cuộc trò chuyện mới)."
"prefs.layout_page.hover_panel_menus":"تبديل قوائم اللوحات عند مرور المؤشر",
"prefs.layout_page.hover_panel_menus_help":"انقر على اسم لوحة في الشريط لفتح قائمتها. مرّر المؤشر فوق اسم لوحة أخرى للتبديل. حرّك المؤشر خارج الشريط والقائمة المفتوحة لإغلاقها."
},
"de":{
"prefs.layout_page.hover_panel_menus":"Bereichsmenüs beim Darüberfahren wechseln",
"prefs.layout_page.hover_panel_menus_help":"Klicken Sie auf einen Bereichsnamen, um das Menü zu öffnen. Bewegen Sie den Zeiger auf einen anderen Bereichsnamen, um das Menü zu wechseln. Außerhalb des Menübands und des geöffneten Menüs schließt es sich."
},
"en":{
"prefs.layout_page.hover_panel_menus":"Switch panel menus on hover",
"prefs.layout_page.hover_panel_menus_help":"Click a ribbon panel name to open its menu. Hover another panel name to switch. Move outside the ribbon and open menu to close it."
},
"he":{
"prefs.layout_page.hover_panel_menus":"מעבר בין תפריטי לוחות בריחוף",
"prefs.layout_page.hover_panel_menus_help":"לחצו על שם לוח ברצועת הכלים כדי לפתוח את התפריט שלו. העבירו את הסמן מעל שם של לוח אחר כדי לעבור לתפריט שלו. הזיזו את הסמן אל מחוץ לרצועת הכלים ולתפריט הפתוח כדי לסגור אותו."
"prefs.layout_page.hover_panel_menus":"포인터를 올려 패널 메뉴 전환",
"prefs.layout_page.hover_panel_menus_help":"리본의 패널 이름을 클릭하여 메뉴를 엽니다. 다른 패널 이름에 포인터를 올리면 메뉴가 전환됩니다. 리본과 열린 메뉴 밖으로 포인터를 이동하면 메뉴가 닫힙니다."
},
"vi":{
"prefs.layout_page.hover_panel_menus":"Chuyển menu nhóm khi di chuột",
"prefs.layout_page.hover_panel_menus_help":"Nhấp vào tên nhóm trên dải công cụ để mở menu. Di chuột lên tên nhóm khác để chuyển menu. Di chuyển con trỏ ra ngoài dải công cụ và menu đang mở để đóng menu."
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"The origin axis is perpendicular to the sketch plane, so it has no line in the plane"
},
"ar":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"محور الأصل عمودي على مستوى الرسم، لذا ليس له خط في المستوى"
},
"de":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"Die Ursprungsachse steht senkrecht auf der Skizzenebene und hat daher keine Linie in ihr"
},
"he":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"ציר הראשית ניצב למישור הסקיצה, ולכן אין לו קו במישור"
},
"ja":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"原点軸はスケッチ平面に垂直なため、平面内に線がありません"
},
"ru":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"Ось начала координат перпендикулярна плоскости эскиза, поэтому линии в этой плоскости нет"
},
"vi":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"Trục gốc vuông góc với mặt phẳng phác thảo nên không có đường thẳng trong mặt phẳng này"
},
"zh":{
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"原点轴垂直于草图平面,因此在该平面内没有直线"
"help.sketch.insert_svg":"Citește traseele unui desen SVG într-o schiță, scalate, rotite și plasate așa cum stabiliți. Folosiți-o pentru a transforma un logo sau un contur vectorial în curbe pe care le puteți extruda.",
"help.sketch.inspect":"Deschide un raport despre schița deschisă: câte grade de libertate au rămas și dacă este complet constrânsă. Folosiți-l pentru a afla ce se mai mișcă înainte de a finaliza schița.",
"help.sketch.intersect":"Adaugă curbele sau punctele în care corpuri, fețe, muchii sau alte schițe taie planul schiței. Rămân legate și urmează modelul când acesta se schimbă.",
"help.sketch.intersection_curve":"Creează curbe 3D acolo unde se întâlnesc două corpuri sau fețe, legate de ele. Folosiți-le ca trasee sau ghidaje care urmează modelul.",
"help.sketch.intersection_curve":"Creează curbe 3D acolo unde se întâlnesc două corpuri, fețe sau curbe ale altor schițe, legate de ele. Folosiți-le ca trasee sau ghidaje care urmează modelul.",
"help.sketch.isoparametric_curve":"Creează o curbă 3D pe direcția U sau V a unei fețe, printr-un punct ales de dumneavoastră, legată de față. Folosiți-o ca traseu sau linie de separare care urmează suprafața.",
"help.sketch.line":"Desenează linii conectate punct cu punct, ca lanț deschis sau contur închis. Este instrumentul de bază pentru majoritatea profilurilor.",
"help.sketch.line.midpoint":"Desenează o linie de la mijlocul ei spre un capăt, celălalt capăt fiind în oglindă. Folosiți-o pentru linii centrate pe un punct, de exemplu o muchie simetrică.",
"material.editor.save_or_discard":"Salvați sau renunțați la ciorna materialului înainte de a atribui un material.",
"material.editor.saved":"Material salvat. Atribuirile pe corpuri rămân neschimbate.",
"material.editor.scope":"Salvarea stochează acest material fără a-l atribui corpurilor. Proprietățile și condițiile lăsate necompletate rămân necunoscute. Densitatea din fișă nu modifică densitatea atribuită și nici masa.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Placă exemplu",
"sample.vents.about":"Canale într-o matrice dreptunghiulară: rândurile, coloanele și pasul sunt parametri",
"sample.vents.name":"Placă de ventilație",
"scale.per_axis":"Pe axe",
"scale.uniform":"Uniform",
"search.command_keys":"Enter execută Shift+Enter execută și păstrează deschis Esc închide",
"Select points of the sketch being edited":"Выберите точки редактируемого эскиза",
"Select the centre the pattern turns around first":"Сначала выберите центр, вокруг которого поворачивается массив",
"Select the curves to divide first":"Сначала выберите кривые для деления",
"Select the curves to offset first":"Сначала выберите кривые для смещения",
"Select the mirror line":"Выберите линию зеркала",
"Select the sketch curves or points to mirror first":"Сначала выберите кривые или точки эскиза для зеркального отражения",
@@ -647,6 +648,7 @@
"The direction must be an axis or a straight sketch curve that lies in the sketch plane":"Направление должно быть осью или прямолинейной кривой эскиза, лежащей в плоскости эскиза",
"The mirror line cannot also be mirrored":"Линию зеркала нельзя одновременно отражать",
"The mirror line must be a straight curve of this sketch, not an origin axis":"Линия зеркала должна быть прямолинейной кривой этого эскиза, а не осью начала координат",
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane":"Ось начала координат перпендикулярна плоскости эскиза, поэтому линии в этой плоскости нет",
"The saved author occurrence is missing. Select a valid author placement before rebinding.":"Сохранённое исходное вхождение отсутствует. Выберите корректное исходное положение перед перепривязкой.",
"Then":"Затем",
"These features will be deleted:":"Эти операции будут удалены:",
"help.sketch.insert_svg":"Читает контуры рисунка SVG в эскиз, масштабируя, поворачивая и размещая их так, как вы зададите. Используйте, чтобы превратить логотип или векторный контур в кривые, которые можно выдавить.",
"help.sketch.inspect":"Открывает отчёт об открытом эскизе: сколько осталось степеней свободы и определён ли он полностью. Используйте, чтобы выяснить, что ещё двигается, прежде чем завершить эскиз.",
"help.sketch.intersect":"Добавляет кривые или точки, где тела, грани, рёбра или другие эскизы пересекают плоскость эскиза. Они остаются связанными и следуют за моделью при её изменении.",
"help.sketch.intersection_curve":"Создаёт 3D-кривые там, где встречаются два тела или две грани, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
"help.sketch.intersection_curve":"Создаёт 3D-кривые там, где встречаются два тела, две грани или кривые других эскизов, связанные с ними. Используйте как траектории или направляющие, следующие за моделью.",
"help.sketch.isoparametric_curve":"Создаёт 3D-кривую вдоль направления U или V грани через выбранную точку, связанную с гранью. Используйте как траекторию или линию разделения, следующую за поверхностью.",
"help.sketch.line":"Рисует соединённые линии точка за точкой, как открытую цепочку или замкнутый контур. Это основной инструмент для большинства профилей.",
"help.sketch.line.midpoint":"Рисует линию от её середины до одного конца, второй конец отражается зеркально. Используйте для линий с центром в точке, например симметричного ребра.",
@@ -1622,6 +1625,7 @@
"help.sketch.pattern.circular":"Копирует геометрию эскиза вокруг центральной точки на полный круг или заданный угол. Используйте для окружностей болтов, спиц и зубьев.",
"help.sketch.pattern.rectangular":"Копирует геометрию эскиза рядами и столбцами с заданным шагом. Используйте для сеток отверстий, пазов или вентиляционных прорезей.",
"help.sketch.point":"Ставит точку эскиза. Используйте как ориентир для размеров, ограничений, центров отверстий или последующих операций.",
"help.sketch.point.divide":"Ставит точки, делящие выбранные кривые на равные по длине части. Точки остаются на каждой кривой и сохраняют шаг при её изменении.",
"help.sketch.point3d":"Ставит точку в пространстве при включённом 3D-эскизе. Используйте как ориентир для 3D-кривых и траекторий.",
"help.sketch.polygon.circumscribed":"Рисует правильный многоугольник вокруг окружности; размер задаётся от центра до середины стороны. Используйте, когда известен размер под ключ, например у шестигранной гайки.",
"help.sketch.polygon.edge":"Рисует правильный многоугольник по двум концам одной стороны. Используйте, когда известны длина и положение стороны.",
"material.editor.save_or_discard":"Сохраните или отмените черновик материала, прежде чем назначать материал.",
"material.editor.saved":"Материал сохранён. Назначения телам не изменились.",
"material.editor.scope":"Сохранение записывает этот материал, не назначая его телам. Пустые свойства и условия остаются неизвестными. Плотность в карточке не меняет назначенную плотность и массу.",
@@ -3750,6 +3755,8 @@
"sample.plate.name":"Образец пластины",
"sample.vents.about":"Пазы в прямоугольном массиве: число рядов, столбцов и шаг заданы параметрами",
"sample.vents.name":"Вентиляционная пластина",
"scale.per_axis":"По осям",
"scale.uniform":"Равномерно",
"search.command_keys":"Enter выполнить Shift+Enter выполнить и оставить открытым Esc закрыть",
"help.sketch.insert_svg":"Учитава путање SVG цртежа у скицу, скалиране, окренуте и постављене како зададете. Користите га да логотип или векторски обрис претворите у криве које можете екструдирати.",
"help.sketch.inspect":"Отвара извештај о отвореној скици: колико је степени слободе остало и да ли је потпуно ограничена. Користите га да сазнате шта се још помера пре него што завршите скицу.",
"help.sketch.intersect":"Додаје криве или тачке у којима тела, лица, ивице или друге скице секу раван скице. Оне остају повезане и прате модел када се он промени.",
"help.sketch.intersection_curve":"Прави 3D криве тамо где се два тела или лица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
"help.sketch.intersection_curve":"Прави 3D криве тамо где се два тела, лица или криве других скица састају, повезане са њима. Користите их као путање или водилице које прате модел.",
"help.sketch.isoparametric_curve":"Прави 3D криву дуж U или V смера лица кроз тачку коју изаберете, повезану са лицем. Користите је као путању или линију поделе која прати површ.",
"help.sketch.line":"Црта повезане линије тачку по тачку, као отворени ланац или затворени обрис. То је основни алат за већину профила.",
"help.sketch.line.midpoint":"Црта линију од њене средишње тачке до једног краја, а други крај се пресликава. Користите је за линије центриране на тачки, на пример за симетричну ивицу.",
"material.editor.save_or_discard":"Сачувајте или одбаците нацрт материјала пре доделе материјала.",
"material.editor.saved":"Материјал је сачуван. Доделе телима остају непромењене.",
"material.editor.scope":"Сачувај чува овај материјал без доделе телима. Празна својства и услови остају непознати. Густина са картице не мења додељену густину ни масу.",
"help.sketch.insert_svg":"Läser in banorna från en SVG-ritning i en skiss, skalade, vridna och placerade där du anger. Använd det för att göra en logotyp eller en vektorkontur till kurvor som du kan extrudera.",
"help.sketch.inspect":"Öppnar en rapport om den öppna skissen: hur många frihetsgrader som återstår och om den är fullt definierad. Använd det för att ta reda på vad som fortfarande rör sig innan du avslutar skissen.",
"help.sketch.intersect":"Lägger till de kurvor eller punkter där kroppar, ytor, kanter eller andra skisser korsar skissplanet. De förblir länkade och följer modellen när den ändras.",
"help.sketch.intersection_curve":"Skapar 3D-kurvor där två kroppar eller ytor möts, länkade till dem. Använd dem som banor eller styrkurvor som följer modellen.",
"help.sketch.intersection_curve":"Skapar 3D-kurvor där två kroppar, ytor eller kurvor från andra skisser möts, länkade till dem. Använd dem som banor eller styrkurvor som följer modellen.",
"help.sketch.isoparametric_curve":"Skapar en 3D-kurva längs en ytas U- eller V-riktning genom en punkt som du väljer, länkad till ytan. Använd den som en bana eller delningslinje som följer ytan.",
"help.sketch.line":"Ritar sammanhängande linjer punkt för punkt, som en öppen kedja eller en sluten kontur. Det är det grundläggande verktyget för de flesta profiler.",
"help.sketch.line.midpoint":"Ritar en linje från dess mittpunkt ut till ena änden, med den andra änden speglad. Använd det för linjer centrerade på en punkt, till exempel en symmetrisk kant.",
"material.editor.save_or_discard":"Spara eller förkasta materialutkastet innan du tilldelar ett material.",
"material.editor.saved":"Materialet sparades. Kropparnas tilldelningar är oförändrade.",
"material.editor.scope":"Spara lagrar materialet utan att tilldela det till kroppar. Tomma egenskaper och förhållanden förblir okända. Kortets densitet ändrar inte tilldelad densitet eller massa.",
@@ -3808,6 +3809,8 @@
"sample.plate.name":"Exempelplatta",
"sample.vents.about":"Spår i ett rektangulärt mönster: rader, kolumner och delning är parametrar",
"sample.vents.name":"Ventilationsplatta",
"scale.per_axis":"Per axel",
"scale.uniform":"Likformig",
"search.command_keys":"Enter kör Shift+Enter kör och behåll öppen Esc stäng",
"help.sketch.insert_svg":"Bir SVG çiziminin yollarını, belirlediğiniz ölçekte, açıda ve konumda bir eskize okur. Bir logoyu veya vektör dış hattını ekstrüzyon yapabileceğiniz eğrilere dönüştürmek için kullanın.",
"help.sketch.inspect":"Açık eskiz için bir rapor açar: kaç serbestlik derecesi kaldığını ve tam kısıtlı olup olmadığını. Eskizi bitirmeden önce nelerin hâlâ hareket ettiğini öğrenmek için kullanın.",
"help.sketch.intersect":"Gövdelerin, yüzlerin, kenarların veya diğer eskizlerin eskiz düzlemini kestiği eğrileri ya da noktaları ekler. Bağlı kalır ve model değiştiğinde onu izler.",
"help.sketch.intersection_curve":"İki gövdenin veya yüzün buluştuğu yerde, onlara bağlı 3B eğriler oluşturur. Bunları modeli izleyen yollar veya kılavuz raylar olarak kullanın.",
"help.sketch.intersection_curve":"İki gövdenin, yüzün veya diğer eskizlerin eğrilerinin buluştuğu yerde, onlara bağlı 3B eğriler oluşturur. Bunları modeli izleyen yollar veya kılavuz raylar olarak kullanın.",
"help.sketch.isoparametric_curve":"Bir yüzün U veya V yönü boyunca, seçtiğiniz bir noktadan geçen ve yüze bağlı bir 3B eğri oluşturur. Yüzeyi izleyen bir yol veya bölme çizgisi olarak kullanın.",
"help.sketch.line":"Bağlantılı çizgileri nokta nokta, açık bir zincir veya kapalı bir dış hat olarak çizer. Çoğu profil için temel araçtır.",
"help.sketch.line.midpoint":"Bir çizgiyi orta noktasından bir uca doğru çizer; diğer uç yansıtılır. Bir nokta etrafında ortalanmış çizgiler, örneğin simetrik bir kenar için kullanın.",
"material.editor.public_reference":"Herkese açık referans",
"material.editor.reference":"Referans",
"material.editor.remove":"Kaldır",
"material.editor.save_or_discard":"Malzeme atamadan önce malzeme taslağını kaydedin veya atın.",
"material.editor.saved":"Malzeme kaydedildi. Gövde atamaları değişmedi.",
"material.editor.scope":"Kaydetme, bu malzemeyi gövdelere atamadan saklar. Boş özellikler ve koşullar bilinmez kalır. Kart yoğunluğu, atanan yoğunluğu veya kütleyi değiştirmez.",
@@ -3807,6 +3808,8 @@
"sample.plate.name":"Örnek plaka",
"sample.vents.about":"Dikdörtgen desende yuvalar: satırlar, sütunlar ve aralık parametredir",
"sample.vents.name":"Havalandırma plakası",
"scale.per_axis":"Eksen başına",
"scale.uniform":"Tekdüze",
"search.command_keys":"Enter çalıştır Shift+Enter çalıştır ve açık tut Esc kapat",
| assets/locales/pending/ribbon-panel-hover.json | SolidCraft contributors | Original ribbon panel menu preference labels and translations | MIT OR Apache-2.0 |
Colour rules and data that any crate may share. It has no dependencies and holds no app types,
so the renderer, the document, the importers and the UI theme all use the same rule and the same
colours instead of keeping copies.
The sRGB transfer function (IEC 61966-2-1) lives here once: every encode and decode in Rust goes
through `srgb`, so a change to the rule is one change. Shaders can't call Rust and keep their own
copies.
## API
| Item | What it is |
|---|---|
| `srgb::encode(linear)` | Linear light (0..1) to an sRGB value (0..1) |
| `srgb::decode(encoded)` | An sRGB value (0..1) to linear light |
| `srgb::encode8(linear)` | Linear light to the nearest 8-bit sRGB code (out of range clamps, NaN reads as 0) |
| `srgb::decode8(code)` | An 8-bit sRGB code to linear light |
| `AnalysisColors` | The surface analysis palette (draft, accessibility, curvature, hatch), straight sRGB 0..1 as the shader takes it; the theme fills it and the renderer reads it |
| `AnalysisColors::DEFAULT` | The light theme's colours with no colour vision mode, as headless pictures use |
The window's own control requests (docs/control-protocol.md), which any egui app answers the same
way: `ui.screenshot` and `ui.resize`. The token files, the loopback server and the request types
are `solidcraft-control`, which has no UI toolkit, so a headless engine, CLI or MCP bridge uses the
control channel without compiling egui. No app types and no engine.
## API
| Item | What it does |
|---|---|
| `screenshot::Shots` | `ui.screenshot`: frames asked for, taken when the view settles, saved and answered, or refused when hidden, cancelled or late; the app gives the reply for no frame |
| `screenshot::encode` | A frame in the format its path names |
| `window::resize`, `window::hidden` | `ui.resize` over `egui::Context`; whether the window presents frames |
# The per-launch control token: 32 random bytes from the operating system (MIT OR Apache-2.0).
getrandom="0.3"
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.