Compare commits

...

161 Commits

Author SHA1 Message Date
bherbruck 0537623a75 Release 0.22.0 2026-10-24 18:53:43 -04:00
bherbruck 2a0096fe4a Release 0.22.0 (#6344)
* Release 0.22.0: version bump, design format 35

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

* Release 0.22.0: tests no longer expect the folded render and material changes to be pending

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 18:53:40 -04:00
dudedagger42 cc9d76cd86 Tapered cut: run on less, then start on the face, when the run on closes a small corner (#4825) (#4858)
A cut starting on a face of its target runs its tool on behind the start plane with the
tool's slope. A taper that widens the pocket narrows the tool there, and a corner arc
smaller than the run on times tan(taper) closed: "the taper closes the profile", though
the pocket itself only grows. The run on is halved, quartered, then dropped (the tool
starts on the face with its straight foot) until the tool builds.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:13:34 +00:00
dudedagger42 c61ffb54a8 Insert Part: M2, M2.5, M14, M24 and M30 screws, bolts, nuts and washers (#2091) (#5194)
The fastener tables stopped at M3 and M20. The socket head cap screw (ISO
4762), hex bolt (ISO 4014), hex nut (ISO 4032) and plain washer (ISO 7089)
tables, and the coarse pitches they use, now have M2, M2.5, M14, M24 and M30
too, in size order. Set screws and dowel pins are unchanged.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:13:09 +00:00
raykholo ab889a8fff History: the sketch at the marker is shown while it's the current step (#5202) (#5204)
* claim: #5202

* History: show the sketch at the marker while it's the current step (#5202)

A sketch a later feature uses is hidden, so moving the History marker
onto it changed nothing in the view. The sketch immediately before the
marker is now drawn even when consumed or hidden; its stored visibility
is untouched, and earlier sketches, hidden components, groups and the
Sketches folder still apply.
2026-10-24 21:12:11 +00:00
dudedagger42 fcc3753a4c Canvas: a plane seen edge-on doesn't take the image (#4717) (#4876)
* claim: #4717

* Canvas: a plane seen edge-on doesn't take the image (#4717)

Placed with a click at a sketch's origin, the canvas went onto the origin's XZ square, which
is under the cursor there too and was the first plane hit. From the Top view that plane is
edge-on, so the image was out of sight and Canvas seemed to do nothing. A plane hit counts only
when the view shows it (its normal more than ~11.5 degrees off the view's edge-on direction); the
sketch plane, or another plane, takes the image instead.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 21:06:41 +00:00
Kai Evans 2c9d0a2847 Sketch palette: Finish Sketch sits inside the footer's inset, label centred (#5387) 2026-10-24 20:52:40 +00:00
Herperlerpicus 99fd05afeb Sketch Dimension: click an existing dimension to edit it while the tool is on (#5451)
With Sketch Dimension on and nothing picked yet, a click on a dimension
that is already in the sketch opens its value box, the same in-place
editor a double-click opens without a tool. Enter applies, Esc cancels,
and the tool stays on for the next dimension.

The dimension's text always takes the click; its lines only away from
sketch points and curves, which still start a new dimension (the rule
plain selection uses). Once a new dimension is under way, clicks keep
their meaning. Dimensions off in the selection filter are left alone.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:52:35 +00:00
CamWight a8051760f7 Create Sketch in the right-click menu (#3032) (#3033)
* claim: #3032

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

* Create Sketch leads the right-click menu on empty space too (#3032)

A right-click on empty space in the viewport now starts with Create Sketch, in
the same place as a right-click on a plane (#1354): first, then a separator.
Over a plane it sketches on that plane, as before; on empty space it is the
toolbar's Create Sketch, so the next click on a plane or planar face starts the
sketch there. The menu offers it once.

Tests: empty_space_leads_with_create_sketch (the list) and
create_sketch_from_empty_space_sketches_on_the_plane_clicked_next (the harness:
right-click empty space, pick it, click XY).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:47:17 +00:00
github-actions[bot] c795a3f581 Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-24 21:40:45 +00:00
Dennis Klappe 2cab9dc142 Measure: copy button next to the Distance value (#4442) 2026-10-24 20:46:51 +00:00
Dennis Klappe 6d8d53aac8 Browser tree: double-click a row to rename it (#4460) 2026-10-24 20:46:49 +00:00
n0tfr3shm1lk 4e6939b5cf Extend: extend a free arc in place and add the line the preview draws (#4615) (#4618)
* Slightly fix the extend tool

* Extend: add the line the preview draws, and stop an arc on another curve

sketch.extend reported "nothing to extend to" whenever nothing stood in the
line's way, even though the viewport previewed a line past its end; with
nothing in the way the extension now runs as far as the preview shows, on the
same measurement, so the click adds what was drawn.

An arc extension that ran into another circle or arc refused the target when
its crossing already belonged to another curve's end (a shared crossing point
of two circles): that point is now joined instead, so the extension closes onto
the geometry that is there.

---------

Co-authored-by: SolidCraft Dev <dev@solidcraft.local>
Co-authored-by: n0tfr3shm1lk <n0tfr3shm1lk@ukr.net>
2026-10-24 20:46:44 +00:00
GonMD f6ff2ff139 Sketch palette: the Tie button shows "Tie", not its catalog key (#4854) (#4857)
* claim: #4854

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

* Sketch palette: the Tie button shows "Tie", not its catalog key (#4854)

The button passed the key itself to ui_kit::button with Button::Normal,
which draws its label as given; it now passes the translated label. The
loose-ends test checks the button by its label.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:46:33 +00:00
dudedagger42 048c1ae8a8 Move preview: draw the moved bodies' own triangles, without evaluating the move (#4912)
A move changes no shape, but its preview copied the session, evaluated
the move and meshed the bodies again for every change of the values. While
the triad was dragged the part trailed the arrow by one of those
evaluations, and longer for a larger part. The preview now takes the
listed bodies' world meshes once and draws them moved by the move's
transform (about the world origin, as the Move feature turns bodies), the
original shown as removed and hidden as replaced; a copy keeps the
original. Anything it can't take (values that don't evaluate, a body not
shown, over 2 M triangles) goes to the scratch-design preview as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:46:29 +00:00
raykholo b00a1e87f4 Insert SVG: refuse geometry scaled to infinity, and bound the profile grid (#5020) (#5025)
* claim: #5020

* Insert SVG: refuse geometry scaled to infinity, and bound the profile grid (#5020)

A transform such as scale(1e308) turned a circle's radius into infinity.
The SVG reader now refuses non-finite transforms and geometry with a clear
error, and Grid::cells in the profile finder uses checked arithmetic, so an
infinite or huge box is checked against all shapes instead of wrapping the
cell count and allocating until memory runs out.

* Insert SVG: refuse lines with infinite ends; fix the grid test's cell count

A line whose transformed ends are infinite was dropped silently, because the
distance between them is NaN. It is now refused as out of range, like curves.

The new grid test expected one cell for a box spanning its own one-box grid;
such a box touches the 2 x 2 cells at its corners, so it expects 4.
2026-10-24 20:46:21 +00:00
Sandmann 8cbde001ef Front … Isometric View commands, Ctrl+1…7 by default (#5057) (#5058)
* Ctrl+1…7 turn the view to the standard views (#5057)

Front, back, left, right, top, bottom and isometric, handled with the
other view keys beside Ctrl+8: animated like the view cube, reserved so
no command can take them, and ignored on the flat pattern page.

* Front … Isometric View are commands, Ctrl+1…7 their default keys (#5057)

The standard views become seven commands (view.front, view.back,
view.left, view.right, view.top, view.bottom, view.isometric) whose
default shortcuts are Ctrl+1…7, instead of keys the window handles
itself. They show in Keyboard Shortcuts, can be moved to other keys or
cleared, and the digits can be given to other commands. In the app they
turn the view as the view cube does; headless they set the session
camera, keeping target, distance and projection.
2026-10-24 20:46:18 +00:00
dudedagger42 ad8b2f91bb File: a saved model whose booleans retried is taken on opening (#5286) (#5334)
A design file keeps its evaluated model so that it opens without
evaluating the timeline. A feature whose boolean needed a retry records
it, and the kernel's BooleanDiagnostics leaves `rejected` out when it is
empty but has no default for it when read, so reading those records
failed ("saved model: diagnostics"), the saved model was refused, and
the design was evaluated on every open (17.6 s for the reporter's
17-feature design, 1.2 ms with this).

The reading side now takes an absent list as an empty one, without a
change to the kernel crate.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:46:06 +00:00
Matt Van Horn 9919825ea3 Expand All and Collapse All in the Browser (#5190) (#5335)
Open or close every Browser folder from the design or a component menu,
including folders that start closed. Display only: not saved, not an undo step.
2026-10-24 20:46:04 +00:00
Kai Evans 97e0ff9050 Sketch: a face seen edge-on projects as one line end to end (#5375)
A face orthogonal to the sketch plane projected every edge onto the same
line: coincident and overlapping pieces, one per notch, that the face
boundary check then refused on the next evaluation ("boundaries cross,
touch or have ambiguous topology") and marked lost.

A link layout whose straight pieces all fall on one line is now one line
between its two farthest points; vertices along it can be projected one
by one. The face-boundary and loop-identity checks skip such a silhouette,
which encloses nothing. Coincident lines are laid out once.
2026-10-24 20:46:01 +00:00
Adam Mikołajczyk c48c1a09d3 fix(skills): quote editing description for valid YAML (#4389) 2026-10-24 20:43:05 +00:00
dudedagger42 ee3d41210d Orbit about the cursor: over empty space, turn about the part in view (#2616) (#4947)
* Orbit about the cursor: over empty space, turn about the part in view (#2616)

With Orbit around → Point under the cursor, an orbit begun over empty space
turned about the middle of everything shown. For a part far from the origin and
anything at the origin (a small body, a sketch), that middle is halfway between,
far off screen, and the part flew out of view: a box at (50000, 50000, 0) with a
10 mm box at the origin went from (872, 535) on screen to (-277084, -279746) after
a 180 px orbit. It now turns about the middle of the bodies the view shows part
of, and only falls back to everything shown when no body is in view.

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

* Restore LF line endings in the files this change edits

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 20:39:54 +00:00
raykholo be685d064f Add a contribution skill for first-time contributors (#5851) (#5852)
* claim: #5851

* Add a contribution skill for first-time contributors (#5851)

skills/solidcraft-contributing walks a new contributor from checking their
tools through fork setup, choosing and claiming an issue, a focused change
with checks and screenshots, and review. The skills index lists it and
the install commands point at tangency-labs/solidcraft.

* Skills install: copy the modelling skills only

The contributing skill is read from its link when someone wants to
contribute, so the install commands no longer put it in every project's
global skills folder.
2026-10-24 20:39:27 +00:00
Karl Von Richter 32e613aa8c Home: test that a sample's picture arriving while it builds is not a failed build (#4351)
#4349 fixed the start page treating a sample's shipped picture, which comes back under the
sample's key without a design, as its build failing. That race dropped the request with "the
sample could not be built" and made the s15_start_page_sample_save_reopen scenario fail
depending on timing. This adds the test that sends the picture while the sample is wanted and
checks that the sample still opens.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:25:58 +00:00
Kai Evans 58a9d33f02 Right-click menus stay open when the opening click's press and release share a frame (#2458) (#2460)
A menu ignores presses until it has been on screen for a frame, so the
press of the click that opened it can't close it again.

Fixes #2458.
2026-10-24 19:25:56 +00:00
raykholo a8e6154c0d Pattern of features: show the direction arrow on the patterned feature (#3034) (#3909)
* claim: #3034

* Pattern of features: show the direction arrow on the patterned feature (#3034)

A rectangular pattern whose Object Type is Features had no drag arrow
and no value box: the canvas anchor knew bodies, faces, edges and
sketch geometry but not a whole feature, so it returned nothing.

A picked feature now anchors at the middle of the geometry it made
(its bodies, or its faces on a body later features changed). The
pattern's preview stands in for the body the feature lies on, so the
anchor reads the feature's geometry including replaced bodies, as a
body pick reads its body.
2026-10-24 19:25:52 +00:00
Mateusz Sych d61dff69a6 Exploded views export as an animation (#3713) (#3744)
* claim: #3713

* Exploded views export as an animation (#3713)

motion.export_animation takes an exploded view (view: id|name) as well as a
study or a joint: the parts come apart at a steady pace from the assembled
placement to where the view shows them, and back together with back: true
(exploded at the middle frame). 41 frames by default, 81 with back. The home
view frames the parts both assembled and exploded. As with explode.show, the
design keeps its placements, its history and its saved state.

explode.show and the export share the moves; moves a design file gives with
no finite translation are left out.

* Mechanisms skill: an exploded view's default frame count

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-10-24 19:25:49 +00:00
jason3w 8c853fff3e Press Pull: route curved faces to Offset Face (#2873) (#3463)
* claim: #2873

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

* Press Pull: route curved faces to Offset Face, with an arrow (#2873)

Q sent every non-edge selection to Extrude, which takes only profiles and
planar faces, so a hole's wall or a boss's side was refused. The engine's
solid.press_pull already offsets curved faces; the window now does too:

- Q on a curved face opens Offset Face; its Faces input takes curved faces.
- Q with nothing selected opens Extrude as before, and a first pick of a
  curved face or an edge reopens Press Pull on it (Offset Face or Fillet).
- Offset Face's arrow follows a curved face's outward normal where it was
  picked, so it can be dragged like an extrude's.

Fixes #2873

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 19:12:40 +00:00
Homero Thompson daec1091f4 Keyboard Shortcuts: a bound Shift key is named "Shift", not "Shift+ShiftLeft" (#4582) (#4657)
* claim: #4582

* Keyboard Shortcuts: a bound Shift key is named "Shift", not "Shift+ShiftLeft" (#4582)

The shortcut's name was the modifiers held plus the key itself, so binding the
Shift key alone showed "Shift+ShiftLeft": the modifier named twice, and the
left and right keys as two different shortcuts.

A modifier key is now the shortcut itself, and its two sides are the same one
(the app's modifiers collapse them), so the page shows what the user pressed.
A shortcut bound before the change ("Shift+ShiftLeft") keeps working: the
stored name is migrated when the preferences or a shortcuts file are read.

* Keymap: the bound-modifier test passes the new repeat flag to press

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 18:59:04 +00:00
raykholo 0520910bfd Control: name the token file and spot a stale one when a call is unauthorized (#4664) (#4670)
* claim: #4664

* Control: name the token file and spot a stale one when a call is unauthorized (#4664)

When the app refuses a token that the MCP client read from a token file,
the error now names that file, when it was written and how long ago,
without printing the token. The app also writes `<port>.pid` beside the
token file (its process id and, on Linux, its start time) and removes
both at exit; when that process has exited, the error says the token
file is stale. The process check runs only after a refusal, and the
token file itself stays one line, so existing clients keep working.

Document force-quit leftovers and the MSIX AppData redirection.

* Bench assistant: read the token out of the new ClientToken

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

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 17:28:12 +00:00
froglemonade2 46f24f2394 Save the browser's view with the design, and an option to hide the origin by default (#1849)
The Origin folder's eye, a finished sketch's Show Profile and Show Dimensions,
and body locks move from UiState into the design's saved visibility
(origin_hidden, hidden_profiles, shown_dimensions, locked_bodies), so each tab
keeps its own and they survive closing the design. The Sketches folder uses the
saved browser folder (c0/sketches in hidden_folders); ui.set/ui.inspect keep
showOrigin, showSketches, hiddenProfiles, shownDims and lockedBodies.

Preferences > Design > "Hide origin by default": a design that never toggled
its Origin eye follows the preference. New fields are a pending format change
(format/pending/browser-view-state.rs).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:15:47 +00:00
raykholo 1236863b17 Coil: its parameters show and can be edited (#3862) (#3915)
* claim: #3862

* Coil: its parameters show and can be edited (#3862)

The Coil dialog now shows every setting solid.coil takes: the section
(circular or square), where it sits on the diameter (inside, center or
outside), the start angle and a Clockwise option. Edit Feature on a coil
reopens the dialog filled in from the feature, and OK redefines it;
before, it fell back to a timeline.edit line in the command search.
Settings left at their default are not sent, so OK on an unchanged edit
keeps the coil as it was.
2026-10-24 17:02:30 +00:00
cronos 4f4b1fc2d2 Sketch: select edge midpoints for horizontal and vertical constraints (#5159) 2026-10-24 16:15:45 +00:00
Rec0iL d5a79a77e6 Agent chat: Pick context and Attach selection for elements, features and bodies (#2286) (#2288)
* claim: #2224

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

* Agent chat: keep Send in view for a long message, open "View attached" (#2224)

The message box sat in a fixed 92-point reserve, so a long message grew it
past the panel and took Send, Stop and Attach view with it. The box is now a
panel of its own at the bottom: it grows upwards up to a cap, then scrolls,
and the conversation takes what is left.

The picture sent with a message is kept on it (once the agent starts on the
message), and the "View attached" caption opens it in a window.

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

* Agent chat: a message's pictures as thumbnails with a hover preview and a viewer (#2224)

Instead of a window on the far side of the screen: the pictures of a message
show as small thumbnails under it, hovering one shows it bigger for as long as
the pointer stays, and a click opens a viewer over the app with the message's
pictures one at a time (previous and next, arrow keys, Esc). A picture carries
a note shown with it, so attachments of other kinds can use the same viewer.

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

* claim: #2286

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

* Agent chat: Attach selection sends what is selected in the model with the message (#2286)

A checkbox beside Attach view. The selection is read when the message is
sent (also for a queued one) and described: faces and edges with the names,
indices and points the tools take plus their size and place, features by name
and kind, bodies by name; at most 64, the rest counted. The agent gets it as
text after the message; the conversation shows the message as typed with the
things as grouped chips (hover for what the agent was told).

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

* WIP: the chat's context picker dialog (#2286)

* Agent chat: Pick context, a picker for elements, features and bodies (#2286)

A button beside Attach selection opens the picker: a dialog with Elements,
Features and Bodies inputs, so clicks in the model, the timeline and the
browser pick into the active one (clicking again takes one out), with the
usual highlight. OK puts the picks as chips with an x above the message box;
they go with the next message like an attached selection (text for the agent,
chips in the conversation) and the tray is empty again afterwards. Cancel
keeps what was picked before. The control channel opens it with pick.

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

* Agent chat: Attach selection counts and describes what was clicked, not the pieces of a rim (#2286)

A click on a circular rim or a split surface selects all its pieces for the
commands. The checkbox counted them one by one and the agent got one entry
per piece (reported on macOS). They are now grouped back into the click they
came from: one chip, one entry with the whole size, and the indices of every
piece in its detail. Edge details are looked up by position as well as
index.

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

* Agent chat: a message's picture is kept on exactly that message (#2224)

The turn now carries its message's place in the conversation (also through a
queue and a send again after a sign-in), so its picture is kept there, once,
and never on an older message that also wanted one (review of #2225).

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

* Agent chat: use the UI kit's checkbox and button and the font tokens (guard tests)

Main's UI-kit guard tests reject raw egui checkboxes, buttons and font sizes in the
chat panel; Attach selection, Pick context, the chip remove button and the chip fonts
now come from ui_kit and Tokens.

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-24 15:57:24 +00:00
bherbruck 5d47946e4d Downloads: say Windows code signing comes from SignPath Foundation (#6207)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 12:30:33 -04:00
github-actions[bot] 69cfd89ae8 Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-24 15:17:54 +00:00
selune 9e6e0b8df1 Render workspace: complete experimental ribbon, resources and effects integration (#5493) (#5494)
* wip(render): reconcile completed material and image pipeline source with 0.20 (#5493)

Source-only integration reference for staged review, not a merge-ready aggregate.
Donor: 0xlillith/solidcraft@aea20b0115.
Upstream: 858e5995d6.

Retain original CPU/material contracts and explicit GPU donor provenance in
docs/render-materials-lab.md (#2067, #2191, #2780, #3306). Preserve released
format migrations and current navigation/startup/shell behavior. New saved
records use pending descriptors. The physical-card descriptor follows the
maintainer's material-engineering-cards naming request, with a legacy-lab
format-29 regression. Replace raw new panel hosts with shared containers.

Repository checks, complete workspace/all-target Clippy, and the independent
physical/resource authoring checks pass for this source. The fresh full local
CI invocation has reached test compilation and is not yet complete. Do not
transfer donor native output or prior test totals to this integration.

Generated review screenshots, video, package ZIP and machine logs remain at
the immutable personal evidence pin. Uncommitted geometric displacement is
backed up separately, not silently included. No kernel/vendor change, direct
main write, release or independent contributor-branch rewrite.

Co-authored-by: Christian Svensson <blue@cmd.nu>

* test(cli): pin the Compact camera-sample admission diagnostic (#5493)

The bounded request still refuses 1024x1024x64 samples before host initialization and writes no output. Update only the stale expected diagnostic to the existing Compact budget wording; production limits and code are unchanged. Exact-tree CLI regression suite: 14 passed, 0 failed. The full repository gate has been invoked separately and is not yet complete.

* Normalize UI container violation paths across platforms

Report repository-relative paths with forward slashes so diagnostics and the existing nested-package regression assertions agree on Windows and Unix. Keep the container detection rules unchanged.

Validation: official ui-containers gate and all seven focused xtask tests passed; rustfmt and diff checks passed. Only xtask/dependencies were compiled; no application build or heavy tests.
(cherry picked from commit 44b58d8e10)

* fix(render): preserve camera contracts and correct acceptance regressions (#5493)

Preserve Design view.apply semantics with a separate strict Render camera command. Retain existing translations when English-only aliases collide; add material-axis entries and camera-specific pitch labels. Compare complete validated engineering cards and retain job handles before cancellation.

Focused and native acceptance is pinned to tree f8a423b21f. Full CI has been invoked separately and is not claimed green. Landing follows the render integration policy in #5493.

* fix(render): resolve showcase licence and upstream resource hygiene (#5493)

Record SychPL's explicit MIT OR Apache-2.0 grant, retaining source/model bytes and the Ubuntu lettering notice. Remove 80 absent attribution rows, personal fork records, dead delivery attributes and three fork/resource-only workflows. Existing xtask assets now performs bounded offline hash checks and rejects orphan render attribution; no parallel draft CI or package installation. Original authoring reproduction remains explicit. Scoped xtask tests, assets, Clippy, formatting and prepared SELENE validation pass; no application or renderer-wide result is inferred.

* fix(render): align saved bounds and progressive sample resolution (#5493)

Address review r4364211669/r4364211677/r4364211681/r4364211691/r4364211695. Persisted light/frame domains respect the existing 1e9 document bound; related LED/rectangle/direction cases use the same bound. Workstation primary resolve shares the 4096 admission cap and film counters use checked conversions. Exact candidate: 485 CPU render tests, 21 render document tests and strict scoped Clippy pass, including positive boundary round trips and 2048/4096 end-to-end checkpoints. No released format/migration edits, relaxed test limits or full-stack acceptance claim.

* wip(render): isolate optional resources and harden sample handling (#5493)

Review work in progress: opt-in encoded render library with reproducible runtime catalog exports; native-only robust dependency through the workspace; checked bloom slices; per-sample point-light diagnostics; independent area-light spatial/film keys. Resource-module binaries in both configurations, generation, Cargo production feature graphs and attribution checks pass. Full engine/render regression and combined acceptance are pending: do not merge this donor as a constituent or treat scoped resource proof as runtime acceptance.

* fix(gpu): preserve donor allocations through the scope-safety repair (#5722)

Port the native-validated #5723 scope-cleanup correction into the experimental donor, retaining its adaptive, volume, aging, photographic and bloom allocations. Same reverse-drain helper and regression cases; the donor combination still needs its own compile/test gate and is not covered retroactively by render-base results.

* claim: #1369

* Reject unknown pending concepts after format folding (#1369)

* test(render): close bounded sampling and bloom review regressions (#5493)

Validate the optional-resource/lighting/dependency donor at its exact tree. Required Vulkan GPU 127/0/3; CPU renderer 487/0; engine Render 72/0; document render 21/0; lean library 7/0; strict scoped Clippy, assets/layers/lists/containers and lean-engine WASM pass. Preserve original failed controller/typing attempts. This is the draft donor/disposition source, not a replacement for constituent render PRs or final main acceptance.

* wip(render): preserve stable aging target validation candidate

Exact staged source from prior review work on donor 3b892eab5e. This is a recovery/review checkpoint, NOT accepted delivery. An actual command-path regression currently fails because the selected executor refuses ordered surface aging. No assertion, capability or admission limit is weakened. Native/UI/full combination acceptance remains pending. Saved/runtime split adds one pending descriptor and retains original file scope; no main/render branch is changed.

* test(render): preserve numeric setup intent at the admitted coordinate bound

Follow up jyviko review on #5494 without weakening the corrected saved-coordinate contract. The positive clicked fixture uses 1e9 instead of inadmissible 2e9; explicit out-of-range cases still refuse, and the full saved setup round-trips through JSON and the native container. Exact-source Render workspace 95/0 and strict all-target UI Clippy pass. This does not incorporate the separately failing stable-aging candidate or claim full current-render acceptance.

* test(render): use the real GPU executor for saved aging

Move successful native cold-reopen Check/Start coverage from the CPU-backed protocol fixture into the production GPU executor tests. Verify capture-index changes preserve the stable target and that enabling aging changes both PNG and raw EXR output. Retain explicit, allocation-free refusal coverage for executors without aging support. Production capabilities and admission checks are unchanged.

* fix(render): persist stable identities for targeted aging (#5493)

Separate saved RenderBodyTarget identities from captured mesh ordinals. Reject legacy ambiguous targets, resolve Check/Start after visibility changes and preserve native reopening, copies and undo. Unsupported executors still refuse; real AMD Vulkan regression completes saved aging after reordered capture.

Source tree 6b84c8ad passes document 23, engine Render 75, GPU effects 5 and Render UI 95 with no failures; scoped strict Clippy, WASM and repository gates pass. Fresh CLI/native reopen and clean/aged GPU exports pass with identical physical totals and inspected actual images. Whole current-render integration is separate.

* fix(render): reconcile combined CPU/GPU contracts on current upstream

Unify captured resources/status and CPU-vs-GPU request schemas after the real two-parent integration; remove obsolete duplicate Vulkan selector and retain combined platform-filter regression. Deduplicate only identical or superseded test definitions, retaining the stronger card metadata and reserved-executor assertions. No assertions, admission limits or feature refusals are weakened.

All targets of render dependencies, GPU, engine, UI, CLI and native app pass cargo check with raytrace-cpu and bundled-render-library at tree c70d00d1. Native executable tests and full current-source acceptance remain pending. This is a private integration rehearsal for subsequent constituent PRs, not an aggregate official render/main merge.

* test(render): retain one complete host lifetime regression set

Deduplicate byte-identical DropProbe/task-lifetime tests and retain the adapted environment/texture ownership regressions with every assertion and complete memory estimate. Keep the registered-geometry admission regression once; only struct field order differed. Replace a one-element diagnostic loop with its equivalent block, preserving all wrap/width assertions.

Test-only integration repair. Formatting and diff checks pass; refreshed executable validation is required. No production behavior, limits, assertions or ignores changed.

* fix(doc): recognize the proven format-29 card descriptor alias

Normalize only the historical format-29 physical-material-cards spelling, and only when material-engineering-cards is registered. Admission and migration de-duplication use the same identity. Unknown concepts, wrong versions and incapable readers still refuse before mutation. Six controlled-history Rust regressions and actual CLI migration/no-clobber acceptance pass. No version, released migration or CI changes. Refs #5793, #1369, #5493.

* test(render): preserve grounding refusal before explicit instance moves

Update the two Render binding fixtures for upstream first-occurrence grounding. Verify grounded movement refuses without changing document/history, then explicitly unground through the public command before the intended move. Retain every target, resource, copy and persistence assertion. Refs #5493.

---------

Co-authored-by: Christian Svensson <blue@cmd.nu>
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: cfaulkingham <colin.faulkingham@gmail.com>
2026-10-24 14:21:59 +00:00
selune b7b25f9e2b Render workspace: first basic delivery to main (#6050)
* Normalize UI container violation paths across platforms (#5599)

Report repository-relative paths with forward slashes so diagnostics and the existing nested-package regression assertions agree on Windows and Unix. Keep the container detection rules unchanged.

Validation: official ui-containers gate and all seven focused xtask tests passed; rustfmt and diff checks passed. Only xtask/dependencies were compiled; no application build or heavy tests.

(cherry picked from commit 44b58d8e10)

Scoped render-branch prerequisite from #4507, coordinated under #5493. No unrelated translation/kernel changes are included. Fresh Windows fmt, seven ui_containers regressions and the production container gate pass on render base 3a6153d18f. Full CI is reserved for final render-to-main acceptance under the maintainer policy.

Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>

* Preserve translated source-label aliases (#5619)

Render integration prerequisite for #5493. Fixes #5618. Retain stable-ID fallback and translated legacy captions; include controlled regression coverage.

* Persist validated engineering cards independently of mass and optics (#1060)

Render constituent under #5493. Fixes #1048. One pending material-engineering-cards descriptor, immutable released migrations, independent assigned density and source conditions. Nine persistence and ten physical-card regressions plus the fresh quick gate pass. Additional native follow-up is reported separately; no full-stack acceptance is implied.

* Render: progressive GPU path-tracing executor (#2067)

Land the unchanged contributor GPU foundation into render under the maintainer policy in #5493. Fresh quick gate 39993339605 passed. Original author and co-author attribution retained; no workflow file changes. Final render-to-main CI and jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Render: asynchronous GPU setup and completed-image panel (#2780)

Integrate the original asynchronous setup, progress and completed-image UI after #2067. Refreshed source passes quick gate 40009901424. Original attribution retained. Final render-to-main full CI and jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* Render: studio lighting, clear coat and glass on GPU (#2191)

Integrate the original studio renderer after the GPU executor and async UI foundations, under #5493. Fresh render-target quick run 40011408339 passes. Preserve attribution, bounded resource admission and documented glass/denoising limits. Historical native benchmarks retain their source pins. Full renderer-to-main CI and jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>

* GPU path tracing: use compatible Vulkan adapters (#3306)

Land the final Vulkan-only path-executor policy after #2191 under the #5493 constituent route. Fixes #3217. Fresh quick run 40012565783 passed. Preserve adapter/software policy and CPU fallback; no DXC dependency or workflow changes. Full native/hardware acceptance and final jyviko sign-off remain separate.

Co-Authored-By: Christian Svensson <blue@cmd.nu>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(material): initialize optional cards in upstream density regression

Fix the E0063 found by quick run 40015318093 while combining main #4785 with render #1060. The new upstream fixture explicitly has no engineering card. Keep every density/name/default-material assertion and production implementation unchanged.

* feat(render): expose independent anisotropic GGX core (#1694) (#1699)

Extract the original numerical source and analytic tests from published #1698 at 463a3e0d into its existing #1699 constituent. Keep current GPU/shared jobs, format and kernel untouched. Add scoped numerical-contract documentation. Full render library 158 passed, strict scoped Clippy and wasm pass, as do assets/layers/lists/format; source tree pinned in the acceptance record. No runtime consumer or final combined renderer acceptance is claimed.

* Sync current main into render, preserving release and viewport changes (#5705)

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* Viewport: whole-body highlights reuse the model's own triangles (#4554) (#5109)

Hovering or selecting whole bodies -- a browser row, a History group, a body
under the pointer in body-pick mode -- built a CPU copy of every body's
triangles into the highlight scene. On a 40-body 4.3 MB STEP assembly that was
147 bodies, 70 MB of triangle copies and a 519 ms frame.

The model already has the same geometry on the GPU. The highlight now names it
and the renderer draws it again in the highlight colour:

- bodies drawn from a display plan: the model's evaluated plan chunks, with a
  hover/selection tint uniform (the chunks stay resident in the model scene);
- bodies drawn as placements of shared triangles: the same placements of the
  model's shared triangles, coloured per instance;
- bodies the model builds on the CPU: their vertex range in the model scene's
  block.

See-through and faded bodies keep the CPU feedback, which colours them; faces
and edges highlighted on their own are unchanged. The highlight looks the same
(verified side by side).

On the assembly the highlight build drops from 519 ms to 9 ms and no longer
copies any triangles; selecting the whole assembly takes 9-20 ms.

Co-authored-by: Claude Code <noreply@anthropic.com>

* Offer to stand up SolidWorks files instead of turning them silently; add Turn Up Axis to the component menu (#5655)

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Sketch on a picked origin plane uses the plane shown after standing an import up (#5659)

* Sketch on a picked origin plane uses the plane shown, also in a moved component

After a STEP file opens its component stays the active one, and standing it up
turns that component a quarter about X. Starting a sketch on XY picked in the
viewport then sent `{"plane": "XY"}`, which names the active component's own
XY: upright after the stand-up. The sketch landed on the world XZ, Look At
turned to face Y and the extrude went along Y.

The viewport and browser only show the design's origin planes, so a picked
origin plane now goes to sketch.create written out as that world plane while
the active component is moved; sketch.create maps it into the component's
frame once. The flip toward the viewer uses the same plane.

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

* Stand-up axes tests: stand the file up through the offer when one is waiting

#5655 offers to stand up SolidWorks files instead of turning them silently.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* gpu tests: give the two test frames the new hover and selection tints (#5698)

#5109 added Frame::hover_tint and selection_tint; two test frames that landed alongside it didn't set them, so solidcraft-gpu's tests stopped compiling on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Release 0.21.0-rc.2 (#5691)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jackson Oursland <jacob.oursland@gmail.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>

* GPU: preserve error scopes on cancelled preparation (#5722) (#5723)

* claim: #5722

* fix(gpu): unwind preparation error scopes on cancellation (#5722)

Move fallible CPU HDR packing before GPU scopes and drain the remaining scope stack in reverse order before propagating denoiser/resource allocation failures. Native regressions cover cancellation and allocation-error cleanup plus subsequent scope reuse. Exact candidate Vulkan-required GPU suite: 101 passed, 0 failed, 3 existing ignored; strict GPU all-target Clippy and repository assets/layers/lists/container checks pass. No driver exception, shader transport or test limit is suppressed.

* library: transport PBR and engineering cards without lossy copies (#1086)

* wip(library): reconcile exact rich-catalog replacement semantics (#1062)

Source-only scoped extraction from original 0f801962 / #1086 on the refreshed #1060 card prerequisite. Preserve current asynchronous picker guards and source-ID handles. Scalar v1 and rich-v2-without-PBR clear existing PBR; supplied rich PBR is retained. No new saved concept or duplicate card descriptor. Formatting and source review completed; compile/native acceptance is still pending.

* test(library): preserve explicit downgrade and fallible UI regressions (#1062)

Request scalar v1 explicitly in the card downgrade refusal regression; rich v2 remains the default lossless writer. Propagate fallible harness actions with ? in the shared clicked-catalog helper, preserving all assertions. No production code, limits or ignore changes. Fresh grouped IO/engine/UI tests, scoped strict Clippy and WASM pass on this exact tree.

* Render: defer path-host startup until first use (#5742) (#5743)

* claim: #5742

* fix(render): defer GPU path initialization until first use (#5742)

Retain the selected adapter factory during Design/safe-mode startup; instantiate once on first Render entry, including late viewport installation. Preserve the deferred/preparing/ready/failed state across document changes and retain selected fallback adapter policy. Exact tree passes 32 render-workspace and 1966 full UI tests (0 failures; 7 existing ignored), strict native all-target Clippy, wasm check and repository gates. Three wasm warnings remain in untouched upstream files. Fresh native desktop acceptance is separate and in progress; no whole-renderer/main sign-off implied.

* GPU: adapt dispatches and retire lost devices (#5754) (#5755)

* claim: #5754

* fix(gpu): adapt dispatch size and retire lost path devices (#5754)

Start with a 64-item quantum and adapt bounded work toward 20 ms from observed completion, including denoiser tiles. Never exceed 16384 work items; hardware-specific shader cost means this is not a universal TDR guarantee. Publish device loss/uncaptured failure/poll failure through live executor availability without reinitializing automatically. Source-pinned required-Vulkan GPU suite: 105 passed, zero failed, three existing ignores, including actual device.destroy retirement and session availability. Strict GPU/engine all-target Clippy passes. Broader engine and repository/WASM continuation remains separately recorded. No geometry baseline, CI rule or published format history change.

* fix(render): wrap complete backend diagnostics (#5714) (#5715)

Preserve full backend/job/decoded-image diagnostics in the existing shared panel without ellipsis. The painted-galley regression covers Floating/Compact and light/dark, complete text, wrapping and fixed panel width. Exact-tree Render UI 33/0, strict UI Clippy, WASM and repository gates pass. Real safe-mode window captures, camera restore, CPU export and normal exit are recorded; no backend behavior or unrelated UI policy changes.

* Sync pinned main into render (#5869)

* Viewport: show reference geometry through bodies (Preferences, off by default) (#2697)

A new Graphics preference, "Show reference geometry through bodies", draws
inactive sketch contours, origin and construction planes and their axes over
shaded bodies. It is off by default, so the viewport looks as before: reference
geometry is depth-tested and a section leaves it whole (#1229); Create Sketch
and dialogs picking a plane still draw the planes through the model.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* ui-egui tests: offscreen GPU tests start their devices one at a time (#3989) (#3990)

* claim: #3989

* ui-egui tests: offscreen GPU tests start their devices one at a time

Every offscreen test started a wgpu instance of its own, and starting one
makes the Vulkan loader scan its drivers. With several test threads two
tests doing that at once crashed the test process with SIGSEGV inside the
loader (loader_icd_scan). offscreen_device now holds a process-wide lock
while it starts the instance, adapter and device; each test keeps its own
device and the rendering still runs side by side.

* DXF: write and read a Bézier at its own degree (#5269) (#5281)

* claim: #5269

* DXF: write and read a Bézier at its own degree (#5269)

The DXF writer and reader held a spline's degree to 7, the value MAX_BEZIER_DEGREE had before
#1642 raised it to 9. A G4 blend (one Bézier of degree 9) was written as a degree-7 SPLINE over
its first 8 control points, so a flat pattern's cut-out stopped 4.444 mm short in the file.

- seg_shape takes its limit from MAX_BEZIER_DEGREE: a Bézier is written whole, at its own degree.
- read_dxf reads SPLINE degrees 1 to MAX_BEZIER_DEGREE, the degrees a sketch spline takes.
- Tests: every Bézier degree 4 to 9, a degree-9 round trip, the reader's degrees, hostile SPLINE
  degrees and too few control points, the G4 cut-out exported and read back with its areas, and a
  degree-9 SPLINE with 2 to 10 points inserted into a sketch.

Fixes #5269

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Renaming a plane keeps its projections, canvases and hidden state (#4353) (#4354)

* claim: #4353

* Renaming a plane keeps its projections, canvases and hidden state (#4353)

timeline.rename only renamed the feature in other features' references. A sketch's
projection of a construction plane, a canvas placed on it and the hidden list keep it
by name too, so they lost it: the sketch warned that the plane no longer exists, the
canvas left the view and a hidden plane showed again. Document::rename_feature_keys
renames it in sketch links and canvases, as rename_body_keys does for bodies; the
selection follows in the command, and the hidden list in Session::refresh_from, so a
hidden plane stays hidden through undo and redo too.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Browser: add Rename and Delete to feature menus (#3839, #4343) (#4342)

* claim: #3839

* fix(ui): add Rename and Delete to browser feature menus

* Browser: a second click on a Document Settings entry closes its window (#4150) (#4152)

* claim: #4150

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

* Browser: a second click on a Document Settings entry closes its window (#4150)

Each entry under Document Settings only opened its window. A second click on
Units closed the menu on the press (the row is outside it) and opened it again
on the release; Precision left Preferences open; Parameters and Design
Properties came back as a fresh copy. Now a click on an entry whose window is
open closes it the way the window's own x does:
- Units: context_menu::toggle_for with the row as the opener, the fix #1522
  made for the workspace button's menu. A right-click still opens it at the
  pointer.
- Precision: prefs::close(app, true), the Cancel/x path that puts back what
  changed since the window opened (now pub(crate)).
- Parameters and Design Properties: the window is dropped, as its x does. A
  Parameters value typed but not applied yet is dropped with it, as today's
  second click already drops it.

Fixes #4150

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Preserve expression-positioned sketch fillet corners (#4097) (#4106)

* claim: #4097

* Sketch fillet keeps a corner placed by an expression (#4097)

Filleting a corner whose position comes from parameter expressions deleted
the point and its expressions, so the sketch gained two degrees of freedom
and later parameter changes distorted the outline. keep_corner now also
keeps fixed or expression-positioned points as virtual sharp corners.

* Fix feature editor picks after upstream sketch resizing (#4083) (#4084)

* claim: #4083

* Feature editors refill named edges and faces where they are now (#4083)

After an upstream sketch change, Fillet, Chamfer, Shell and Draft evaluated
correctly, but their editors refilled the originally clicked points and
could then fail with "no edge at ...". Resolve the stored names through the
document's naming helpers first, keep the resolved index, and map the pick
into the displayed body's frame; the point lookup stays as the fallback.

* ui-egui tests: clear the texture deltas of the frames they run (#4039)

Nine tests dropped an egui frame's output with its texture delta still
unapplied (the expression completion editor's frames, the slot preview's
three, the browser's feature rows). epaint asserts against that in debug
builds, so they failed under the dev profile and passed only where debug
assertions are off. They clear the delta as the other tests do; one more
hover test that dropped its output the same way does too.

* Frames: profile library, members along frame sketch lines, and the Frame Member dialog (#4003) (#4004)

* claim: #4003

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

* Frames: the profile library (#4003)

The first slice of frames and weldments: the cross-sections structural members
will be made of, each SolidCraft's own parametric definition built in code
(no vendor or standard size tables are shipped).

- solidcraft_doc::frame: bar, round bar, rectangular and round tube, angle,
  channel, I-beam and a generic T-slot extrusion as exact line/arc regions,
  with defaults worked out from the profile's size, refusals that name the
  dimension that does not fit, anchors and placement, and exact section
  properties (area, centroid, Ixx/Iyy/Ixy by Green's theorem).
- frame.profile.list, frame.profile.properties (with mass per metre for a
  density) and frame.profile.insert (draws a profile into a sketch, locked by
  default, at a point, anchor and angle).
- Tests against hand-computed values (rectangles, circles, composite
  rectangles by the parallel-axis theorem, fillet spandrels), hostile input,
  extrusion volume, undo; catalog entries in every locale; docs/frames.md;
  ROADMAP M15.

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

* Frames: drop the built-in T-slot profile (#4003)

Aluminium extrusions and other vendor sections come from the user's own
sketch (drawn or inserted from the vendor's DXF), so the library keeps the
plain steel shapes only.

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

* Frames: members along frame sketch lines (#4003)

frame.member lays one body per straight line of a frame sketch (2D lines or
3D sketch lines), its cross-section square to the line from end to end:

- profile: a built-in profile, or profile_sketch, a sketch whose material
  (nested loops alternating, so two circles make a tube) is the section in
  its own x and y around its origin; profiles picks override it.
- anchor (origin, centre, centroid or a bounding-box corner or edge middle),
  angle (an angle expression, so a parameter), mirror.
- Orientation: the profile's y as near world up as the line allows, x
  square to both; a vertical member's x is world X (frame::member_axes).
- Members follow sketch and parameter edits, undo as one step, save and
  reopen (format: pending/frame-members.rs), and are named after the profile
  (Rect Tube 40x40x3, (2)…), numbering on past names already taken.

Tests against hand-computed volumes and bounding boxes (rectangle, 3D
tower lines, parameter edit, sketch tube profile and picks, anchor/angle/
mirror), refusals, undo/redo and reopen; walk fixtures and goldens; docs.

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

* Frames: fixes from the adversarial review (#4003)

- Default member lines are what was drawn: projected and other linked curves
  and 3D wires (a face sketch's edges) and lines of no length are left out.
- A profile sketch's material starts from its drawn profiles, as
  solid.extrude's "all" does, so a section sketched on a face works; the
  even-odd test uses outlines and boxes worked out once, capped at 256.
- Member names are pinned to their lines (`names`, by line id; `body_name`
  is the base): deleting or adding a line, or deleting another feature, no
  longer renames other members, and frame.member stores every name it gives.
  body.rename renames a member on its line (also for later member features);
  a copy whose body_names were set still names its members by position.
- Built-in profile dimensions are expressions (ProfileSpec): they follow
  parameters, bare numbers take the design's units, and they are feature
  inputs. frame.profile.properties/insert read them the same way.
- Default root/toe radii shrink to the room a thick section leaves; radii
  given are checked by name; a radius must be 0 or at least 0.01 mm; error
  messages report the wall or flange value given, not twice it.
- A line within 1e-6 rad of vertical orients as vertical, so rounding never
  turns a post a quarter turn.
- A 2D frame sketch in a differently placed component is mapped into the
  member's frame, and member features recompute when placements change.
- Caps before lookups for a design's `lines`; a line listed twice is
  refused; a 3D wire that runs back on itself is not a line.

Regression tests for each, from the review's scenarios.

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

* Frames: the Frame Member dialog on the ribbon (#4003)

- Frame Member (MODEL > Create) opens a dialog: the frame sketch's lines
  picked in the view (or All Lines), a built-in profile and its dimensions or
  a section picked in another sketch, anchor, angle, mirror and name, with a
  live preview. Edit Feature reopens it filled in.
- Lines picked in the view keep their sketch (a SKETCH_LINES input picks
  ExternalSketchCurve): line ids are each sketch's own, so two 3D sketches
  both have a `w2`, and the members went on the newer sketch's lines.
- timeline.redefine takes frame.member. Members keep their names through an
  edit unless it gives them a new body_name; frame.member takes `names`.
- A member whose section is a whole profile sketch keeps it when edited
  (the dialog shows the sketch instead of picking every region, which would
  fill a tube's hole).
- An original frame_member icon, open for review, on the ribbon, timeline
  and browser.
- The angle profile is listed as L-Angle, and web and flange as Web/Flange
  thickness, so they translate apart from the existing Angle, Web and Flange;
  the dialog's labels are in en, ja and zh. en-GB no longer repeats the frame
  command labels it spells the same.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Rectangular and circular pattern: copies that miss the body are reported (#3042) (#3919)

* claim: #3042

* Warn when cut pattern copies remove no material

A rectangular or circular pattern whose copies miss the body succeeded silently and those copies did nothing. The pattern now reports how many of its cut copies removed no material, and keeps the copies that land.

* Delete in a rolled-back history warns about the features after the marker (#3471) (#3698)

* claim: #3471

* Delete in a rolled-back history warns about the features after the marker (#3471)

With the timeline rolled back, the checks a delete runs first (the
confirmation's "these features will fail", timeline.dependents and
selection.delete's would_fail) compared models evaluated only up to the
marker. Features after it, broken by the delete, were not listed, so their
error marks turned up only once the timeline moved to the end.

newly_broken_in_history evaluates both sides over the whole history when
either has a marker (on copies; the model and document are unchanged) and is
used by the delete's report and by timeline.dependents.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* macOS: Fusion navigation preset does not support Shift + two-finger orbit (#3446)

* Macos: keep touchpad/key modifiers true to fusion

* fix: remove obsolete orbit snap reset from trackpad navigation

Upstream removed ViewportState::orbit_snap. The new trackpad orbit path retained one reference, causing E0609 in the PR merge build.

Validation: all seven trackpad tests pass; cargo check -p solidcraft-ui-egui --all-targets passes on the exact failing CI merge 66a2791b with this fix applied; rustfmt and diff checks pass.

Co-Authored-By: GitHub Copilot <noreply@github.com>

* fix: retain Fusion trackpad help in refactored preferences

Preserve upstream's new preferences pages and apply the translated Fusion trackpad tooltip in prefs/navigation.rs.

Validation: cargo build -p solidcraft passed. Tests and full CI were not run, as requested.

Co-Authored-By: GitHub Copilot <noreply@github.com>

* style: format Fusion trackpad preferences tooltip

Apply rustfmt's required wrapping for the relocated tooltip. No behavior changes.

Validation: cargo fmt --all -- --check and git diff --check passed.

Co-Authored-By: GitHub Copilot <noreply@github.com>

---------

Co-authored-by: GitHub Copilot <noreply@github.com>

* 2-point circle takes a typed diameter (#3278) (#3335)

* claim: #3278

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

* 2-point circle takes a typed diameter (#3278)

After its first point the 2-point circle tool showed no value box, so the
circle could only be sized with the cursor. It now shows a Diameter box,
like the centre circle: the typed value puts the second point that far
along the cursor's direction, and Enter draws the circle.

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Reorder design tabs by dragging them (#3187) (#3188)

* claim: #3187

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

* Reorder design tabs by dragging them (#3187)

A tab dragged along the strip follows the pointer while the others make
room, and lands where it is released; Escape, or releasing down the
window, leaves the order as it was. With the custom title bar a drag that
starts mostly up or down (or on the only tab) still moves the window.
The control channel's ui.documents gains a move action (from, to).

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Renaming a body keeps the evaluated timeline, off the window thread (#2467) (#2468)

* Renaming a body that later features change evaluates nothing (#2467)

Model::rename_body now keeps the results when later features change the
body (a pattern, a chamfer, a join). It still evaluates when a body of
that name comes and goes or isn't the same body throughout, when a later
body's name is worked out from it (X (2)), or when a message quotes it.

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

* Rename box: rename off the window thread, and locked bodies follow a rename (#2467)

The rename box commits body.rename through run_heavy, so a rename that
still evaluates later features (one names the body in its targets)
computes beside the window instead of freezing it. Locked bodies now
follow a body.rename from anywhere (the rename box, the control channel,
scripts), not only the rename box.

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Toolbar: a constraint dragged out of the small box becomes a full button (#5254) (#5456)

* claim: #5254

* Toolbar: a constraint dragged out of the small box becomes a full button (#5254)

The Constrain panel's small box of constraint icons was a fixed list, so
dragging one onto the full-size buttons saved a new order but it was still
drawn in the box. The toolbar settings now remember, per tab, which box
commands were moved out; those are drawn as full buttons, the box closes
up, and dropping one back on a box icon returns it. Reset Toolbar restores
the default box, and settings saved before this load unchanged.

* Sketch offset keeps its source lines free to turn (#4464) (#4466)

* claim: #4464

* Sketch offset keeps its source lines free to turn (#4464)

An offset adds a Parallel between each source line and its offset line.
Since offsets became associative, the offset's points are held during a
solve, so each Parallel pinned its source line to the offset line's
current direction: a closed chain lost its turning freedom (8 degrees of
freedom became 6), and Horizontal or an angle edit on a source line was
refused as conflicting.

The solver and the constructive reading now leave out a Parallel that
ties an offset line to the source it follows while the association lives;
the association already keeps them parallel. After Break the stored
Parallel drives again.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Flat Pattern view draws perforations (#4440) (#4441)

* claim: #4440

* Flat Pattern view: draw perforation holes (#4440)

The flat view drew the outline, cut-outs, pockets and bend lines but not
perforation fields, though the flat-pattern data and the DXF export list
their holes. The view now reads the hole centres from the sheet through
SheetBody::perforation_holes, caches them per model state and body, and
draws them as circles in the cut-out style. Holes outside the viewport or
smaller than one pixel are skipped, and the cache is capped at one million
holes.

* "Don't Save" looks like a button in the Save changes prompt (#4380) (#4400)

* claim: #4380

* "Don't Save" looks like a button in the Save changes prompt (#4380)

The prompt's third action used the kit's low-emphasis text style, so it
didn't read as clickable and people thought Cancel was the only way out.
Add an opt-in secondary_left style to the confirm footer (a framed
secondary button from the theme tokens) and use it for Don't Save.

* Save prompt test: clear each frame's texture delta once it is applied

egui 0.36 panics when a TexturesDelta with unapplied deltas is dropped;
the test kept only the last frame's output and dropped the earlier ones.

* 3D mouse test: clear the frame's texture changes, so it passes in debug builds (#4382)

each_frame_hands_the_view_its_motion_through_the_drivers_mapping dropped
the output of a frame it ran (`let _ = ctx.run_ui(...)`). epaint panics
in debug builds when a TexturesDelta is dropped with deltas left ("Dropped
TexturesDelta with 1 unapplied deltas"), so the test failed under a plain
`cargo test` on every platform while the ci profile passed. It now clears
them, as the other UI tests do since #60.

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Spanish (es-ES): catch up with en.json (#4292)

Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>

* Esc ends the navigation bar's Pan, Orbit and Zoom modes (#4226) (#4233)

* claim: #4226

* Esc ends the navigation bar's Pan, Orbit and Zoom modes (#4226)

The nav bar toggled a mode on, but nothing cleared it on Esc, so a left drag
kept panning, orbiting or zooming. Esc now ends an active nav mode first;
the next Esc reaches the tool or dialog as before.

* Keep long Select Other labels inside the popup (#4209) (#4225)

* claim: #4209

* fix(ui): bound Select Other labels and expose full names (#4209)

* Deleting an inserted file removes its components, placements and joints (#4100) (#4113)

* claim: #4100

* Deleting an inserted file removes its components, placements and joints (#4100)

A STEP or mesh file inserted into a design makes a component tree. Deleting
its feature left that tree behind: the components, their occurrences, the
rigid joints that hold them and the bodies' component assignments. A later
body that took an imported body's name was then placed in the stale component.

Document::remove_component_tree removes a component subtree and everything
that refers to it: the features made inside it, its occurrences, the joints
on them with their motion links, joint origins, contact set members, motion
study keys and exploded moves, body component and offset entries, standard
part and link records, and configuration columns. Deleting an inserted
file's feature and component.delete both use it; component.delete now also
removes joints left pointing at a deleted occurrence. The body transfer
refusal moves with it, unchanged.

* Only an inserted file's feature takes its component tree when deleted

Mark the feature a STEP, IGES or mesh insert makes (`inserted`), and remove
the component tree on delete only for those and for a linked design's
feature. Deleting a copied component's independent base deletes just that
feature, as before. Older designs get the mark as they open (pending format
change), except copied components' independent bases.

* Rigid joint saved with the wrong occurrence when two occurrences share a component (#3857) (#3939)

* claim: #3857

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

* Joint dialog: a body of a further instance picks that occurrence, not the component's first (#3857)

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Cancel Sketch: leave a sketch without keeping its changes (#3838) (#3938)

* claim: #3838

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

* Cancel Sketch: leave a sketch without keeping its changes (#3838)

sketch.cancel (Cancel Sketch, ribbon Exit group and the sketch's
right-click menu) puts the design back as it was when the open sketch
was created or opened for editing: a new sketch is removed with its
dimension parameters, an edited one returns to how it was. It is one
undo step, so Undo goes back into the sketch.

The session remembers the design and the undo history at the point a
sketch is entered. Cancel uses that only while the history still runs
through it (the same steps before, every step since taken in this
sketch); otherwise it refuses and points to Undo, so a stale entry can
never throw away later work.

The icon is the approved close glyph in the sketch accent (alias
sketch_cancel); ja and zh carry the new label.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* 3MF import: name a Bambu Studio project's bodies after its objects (#3858) (#3859)

* claim: #3858 (3MF import: names from a Bambu Studio project)

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

* 3MF import: name a Bambu Studio project's bodies after its objects (#3858)

Bambu Studio leaves the name off the root model's objects and keeps it in
Metadata/model_settings.config (<config><object id="N"><metadata key="name">),
so every object of a project came in as Mesh1, Mesh2, ... A build item whose
root object has no name of its own now takes the object's name from those
settings. A name in the model still comes first, and part names, other
settings and objects of other model parts are left alone.

The settings are read only when a body needs a name, within what the model
parts left of the 1 GB budget. Settings that can't be read (malformed, nested
too deeply, too large) never fail the import: the bodies keep the generic
names, with a warning saying why.

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

---------

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Viewport: right-click keeps a multi-selection of faces and edges (#3733) (#3788)

* claim: #3733

* Viewport: right-click keeps a multi-selection of faces and edges (#3733)

---------

Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>

* Parameter import and image pickers offer their own file types (#3216) (#3314)

* claim: #3216

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

* Parameter import and canvas image pickers offer their file types (#3216)

Both opened the picker with no file type of their own, so it listed only
designs, CAD files and meshes (and All files). macOS lets you pick only
the types a filter names: CSV parameters, and PNG or JPEG images for a
canvas, were greyed out. They now ask for csv/json and png/jpg/jpeg,
which the picker lists first (#1801).

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Return to the originating tab after finishing a sketch (#3250) (#3264)

* claim: #3250

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

* Return to the originating toolbar tab after finishing a sketch (#3250)

A sketch started from SHEET METAL or PLASTIC always ended on the SOLID
tab. Remember the tab that was active when the sketch began
(UiState::sketch_return_tab, not saved) and go back to it when the
sketch ends. Falls back to SOLID when that is not one of the normal
toolbar tabs. A sketch started from SOLID behaves as before.

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

* Test: no one-element loop in the return-tab test (clippy single_element_loop)

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* STEP import: let a cancelled import release the import slot (#3171) (#3189)

* claim: #3171

* Release cancelled file import slots (#3171)

* Dragging a part along its joint no longer highlights what's under the pointer (#5137) (#5140)

* claim: #5137

* Dragging a part along its joint no longer highlights what's under the pointer (#5137)

The viewport's hover pick was skipped during a box selection and camera
moves, but not while a jointed part was dragged, so faces and edges under
the pointer lit up on every frame of the drag. The pick now waits until
the joint drag ends.

* Browser: a body picked in the viewport is opened to and scrolled to (#3005) (#3081)

* claim: #3005

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

* Browser: a body picked in the viewport is shown in the Browser (#3005)

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

* Merge main; viewport::select takes a toggle flag now

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Line tool snaps a diameter through the circle centre (#4326) (#4327)

A line drawn across a circle only snapped its end onto the curve, so a
diameter could miss the centre at any angle. Near the centre the line now
locks onto the radial line: on the rim it stays on the circle and the centre
is kept on the line, and on the centre point the end lands there.

* Flatpak: find the host's fonts for CJK text and the font list (#5124) (#5127)

* claim: #5124

* Flatpak: find the host's fonts for CJK text and the font list (#5124)

Inside the Flatpak sandbox the host's fonts are mounted at /run/host/fonts
and /run/host/user-fonts, which no font search looked at. Japanese and
Chinese text fell back to boxes, and the font list missed the host's
families.

- Search both folders for the CJK and Chinese fallbacks and for the
  installed-font list.
- The CJK fallback reads SOLIDCRAFT_FONT_DIRS (a path list, as the font
  list already does), with CRAFT_FONTS_DIR kept as a fallback.

* Crash reports: keep component.activate in the journal (#5079) (#5121)

* claim: #5079

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

* Crash journal keeps component.activate (#5079)

Activating a component changes only the session's active component and
occurrence path, not the design, so the journal dropped the command and a
crash report's replay built later parts under the previously active
component. The journal now also keeps a command that changes either.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Control channel and MCP on by default, with a setting to turn it off (#5561)

The app opens its localhost control channel on port 27878 (or the next free
one) unless Preferences > Assistant turns it off; --control and
SOLIDCRAFT_CONTROL_PORT still win. A current.port discovery file lets
solidcraft-cli mcp find the running app with no arguments.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Tests: moving an occurrence recomputes only the cuts and joins it touches (#4965) (#4982)

* claim: #4965

* Joins and cuts follow only the placements they depend on (#4965)

Tests that turning a joint between parts cuts no hole again, and that a
tool cutting another part cuts again when that part moves relative to it,
while an unrelated part moving keeps the result.

Booleans and Combine now hash only the placements of the bodies they take
as inputs, relative to the frame that evaluates them (#3397), so these
pin that behaviour for joins and cuts with named targets across parts.

* fix(ui): preview shortcut chords and wait for complete modifier input (#1810)

Keep physical modifier events out of the ordinary-key slot, preview held keys immediately, and confirm after release and a one-second settling interval. Preserve legacy control-channel modifier behavior while allowing explicit physical Control/Command input. Add regression coverage, a UI scenario, localized guidance and documentation.

Fixes #1809.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>

* Move: scope box selection to the active component (#4459) (#4527)

* claim: #4459

* fix: scope Move box selection to active component (#4459)

* Delete removes a selected construction axis (#4692) (#4695)

* claim: #4692

* Delete removes a selected construction axis (#4692)

Delete on a construction axis skipped it with "the origin axes can't be
deleted". It now deletes the construction axis feature, like construction
planes; the origin axes X, Y and Z are still refused.

* Agent chat: end the agent's process group where `kill` is busybox' (#4587) (#4588)

* claim: #4587

* Agent chat: end the agent's process group with the system call (#4587)

The group was signalled with a `kill` program: `kill -s TERM -- -<pgid>`.
That works with procps' `kill` and with BSD's, but busybox' `kill` takes no
`--`: it prints its usage, exits 1 and signals nothing. The agent's helpers
(the CLI an adapter wraps, the MCP servers it starts) then stay alive after
the agent is ended.

The group is signalled with the system call instead, so no `kill` program and
no shell are needed and the behaviour is the same everywhere. `nix` (MIT) is
already in the tree through zip, and only on Unix: the browser build does not
take it.

The two tests that cover this now pass where they failed before:
`agent_chat::process::tests::dropping_the_process_ends_its_group` and
`agent_chat::process::tests::stopping_the_agent_leaves_no_helpers_running`.

* Extrude: picking a face, vertex or parallel plane off the profile switches the extent to To Object (#2855)

* Extrude: picking a face, vertex or parallel plane off the profile turns the extent to To Object

With profiles picked, a face off their plane (or curved), a vertex off it, or a
construction plane parallel to it is taken as the To Object target instead of
another profile: the extent switches without the Extent Type choice, and the
distance shows how far the target is. Coplanar faces still join the profiles.

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

* Extrude: automatic To Object is a Preferences setting, off by default

Preferences > Design > "Extrude to a picked target" (`extrude_auto_to_object`, default
false) turns the automatic To Object on. Off, picks in the Extrude dialog only add
profiles and the distance is not synced to a target, as before this change.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* FreeCAD/Rhino import: decode caps with room for detailed parts (#1783) (#3209)

* claim: #1783

* FreeCAD/Rhino import: decode caps with room for detailed parts (#1783)

cadmpeg decoded FreeCAD and Rhino files under its service profile, whose
million collection items and 2.56e8 work units a detailed part (a sprocket,
a threaded rod, a part with a few hundred faces) runs past: the import was
refused with "resource limit on CollectionItems: BudgetExceeded". Allow
8 million items and 2e9 work units, half of cadmpeg's desktop profile; the
input-size cap stays SolidCraft's STEP limit and every other cap stays at
the service profile's.

decoding_is_capped checks the two raised caps (and that they stay below the
desktop profile's) and that every other cap is still the service one.

Fixes #1783

* A spherical shell revolves into a solid with a void, and cuts and joins with hollow tools work (#451) (#4182)

* Assembly: ground the first part; drag free joint chains (#2804) (#2805)

* assembly(feat): ground first parts and drag free joint chains

Ground the first top-level part when it is created and let face dragging reposition free occurrences or any member of a floating joint chain in one undo step. Preserve exact repeated-placement frames, joint values, modifier selection and Escape cancellation. Keep the accumulated fixture, viewport layout and documentation integration fixes from the existing PR.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(fix): update movable fixtures for first-part grounding

Accept the documented grounded override in component creation, explicitly free movement fixtures, and keep linked clipboard pastes movable even in an empty document. Cover first and reused linked pastes, default insertion grounding, and single-step undo in the existing link tests.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): keep the motion-cost crank free

Unground the first component in the document-level joint-face measurement fixture so its motion samples can rotate after first-part grounding. Preserve the rotation and shared-measurement assertions that caught the Linux CI failure.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): free the frame placement fixture

Create the frame-member placement fixture ungrounded before moving it above its root sketch. Keep its world-space member assertions while respecting first-part grounding in the frame tests added on main.

Signed-off-by: Karl Von Richter<karl@embeint.com>

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>

* UI: integrate theme radii, borders, palettes and viewport refresh (#5045) (#5046)

* claim: #4922

* UI: let themes own corner radii

* UI: use four-point panel and window radii in SolidCraft 95

* UI: align the 95 radius assertion with four-point panels

* claim: #4953

* UI: let themes own structural and control borders

* UI: single Blueprint outlines and dotted secondary rules

* UI: separate container outlines and add theme-owned 95 bevels

* claim: #5007

* UI: apply theme palettes to icons, workspace tabs and Gruvbox settings

* claim: #5045

* Viewport: invalidate cached geometry colours when the theme palette changes

* UI: preserve legacy theme logo colours with the borderless mark

* Section Analysis: OK with no plane picked turns the section off (#2410) (#2411)

While a section is on, Section Analysis opened with no plane picked now
says "No plane picked: OK turns the section off." and OK runs
inspect.section {"clear": true}. With no section on, OK with no plane
still asks for a plane.

Fixes #2410.

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* Close Change Parameters with Escape (#3372) (#3399)

* claim: #3372

* Fix Escape cancellation in Change Parameters

Consume Escape before parameter editors can commit pending text on focus loss, while preserving autocomplete and shortcut capture priority. Add 11 UI regressions covering draft cancellation, preview cleanup, document and history preservation, and reopening saved values.

* Preserve construction references when axes and points are renamed (#4419) (#4449)

* claim: #4419

* fix: preserve renamed construction references (#4419)

* Browser: route body picks into open command dialogs (#2560) (#4371)

* claim: #2560

* fix(ui): route Browser body picks into active dialogs

* Fix midpoint picks in sketch dimensions (#4872)

Retain MID snaps across both picks and materialize reusable midpoint
constraints for point-to-line and point-to-point dimensions. Let midpoint
pairs choose horizontal or vertical spans with pointer placement before
entering a value.

Cover projected model edges, overlapping geometry, helper reuse,
undo/cancel/reopen, and existing angular and line-circle workflows.

* Sketch slots: provide typed dimension inputs for every variant (#2423) (#4318)

* claim: #2423

* fix(sketch): add typed dimensions to every slot tool (#2423)

* Version 0.21.0 on main (released from rc.2's code) (#5774)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* docs(mcp): Windows setup for Claude Desktop (#3794)

* docs(mcp): Windows setup for Claude Desktop (paths, two servers, Store config file)

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

* docs(mcp): say the Store build can keep the config elsewhere, not that the other path fails

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* MCP: wait out long requests, honest batch outcomes, batch tool aliases (#2901)

Rebased onto main as one commit (was 120d26f7, df488f9a, db9d1073 plus
merges of main).

- A request the app reports as still running (#1618) is waited for on the
  same connection through `control.pending`; past the wait the error says it
  was not cancelled and will still apply (or only write its file), later calls
  are not sent behind it, and the new read-only `pending` tool returns its
  result once it lands.
- A failed batch says what really happened: `rolled_back` only when the
  design is back where the batch started (as #3288 / #3242 on main: the app's
  `rolled_back` is believed; for an older app, every completed step undone;
  only a rollback asked for is reported as one), plus `applied`, `not_run` and
  `failed.still_running`.
- A batch step may name a tool (set_parameter, list_commands,
  describe_command, execute, measure, inspect_design, undo, redo, export, save,
  open, new_design, crash_reports); screenshot, body_topology, batch and
  pending are refused before anything is sent.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* 3D mouse: driver choice, guided calibration, button mapping and a picture of the device (#1463) (#3825)

* Add a small test boat to steer around with the 3D mouse

A little hull in its own camera, drawn by the UI itself and not the CAD
viewport, so a 3D mouse can be tried out without opening a design. It
brings the model (generated by assets/models/make_test_boat.py) and the
tests for how the camera moves it.

Nothing uses it yet; Preferences picks it up with the rest of the 3D mouse
work.

* UI kit: keep a choice row from flipping with the scrollbar

A choice row picks segments or a dropdown by whether its labels fit. A page
scrollbar that comes and goes narrows the row by its own width, so the row
could flip back and forth between the two on a page that sits right at the
edge. It is now judged on the whole row and through a small helper,
fits_steadily, that only changes its answer once the miss is wider than the
scrollbar.

Also tests for that, for typed numbers in a slider's value box, and a
test hook to put up the issue-report input barrier by hand.

* 3D mouse: device model, guided calibration, buttons and a picture

The 3D mouse becomes a device we know something about instead of six raw
numbers.

The reader picks between spacenavd and the direct hidraw driver, falls back
from one to the other, and says so when it can't open the device. Guided
calibration asks whether the device is a spring cap or a trackball, adds a
touch step, and uses a settle filter so a ball let go of stops reading as
motion. Buttons get jobs (keys, held modifiers, commands), saved per
device, with defaults for the SpaceMouse Pro.

Preferences gets a picture of the device that moves as it does, next to the
test boat, and the same picture can sit in the 3D view at Left Viewport,
Right Viewport or Left Window, above the floating History card like the
legend and readouts.

These ride together because the crate's calibration types, the Preferences
page, the Linux reader and the button hook all use each other.

* 3D mouse: offer to calibrate an uncalibrated device

When a 3D mouse shows up that has not been calibrated, a card in the 3D
view offers to calibrate it, to swap drivers when the other one would work
better, to close, or to never ask again. It is made once per device, goes
away after 30 seconds in view, and gives way to a status or computing
toast instead of stacking on it.

The card is the shared toast, which now takes buttons: they group by
purpose, wrap inside the card at narrow widths, and do nothing while an
issue report holds CAD input. The picture of the device in the 3D view
keeps clear of the card.

* 3D mouse: translations and attribution

The 3D mouse strings (ui.3d_mouse_*, the calibration steps and the offer
card) in every shipped language, the Japanese catalog note, and the credit
for the test boat model in ATTRIBUTION.md.

This is last because the code before it only looks keys up: it builds and
runs without these, falling back to English until they land.

* 3D mouse: draw its preferences with the settings kit like the rest of Navigation

The 3D mouse section was a folded header with its own layout. It now
uses the same kit as the sections above it: a settings_section card,
settings_form rows with the shared label column, dividers between the
device, axes, picture and buttons groups, Text::Hint for the helper
lines, and settings_control_width for the dropdowns.

The section is open like every other one (the header still folds it),
so the device badge on the folded header is gone; the calibration row
already names the device. Every setting, string and key is unchanged.
Tests that unfolded the section no longer click it, and the hint lines
are looked up as hints instead of notes.

* 3D mouse: show each axis's live tilt in Preferences, like main's axis rows

Each view axis row is main's axis row again: a meter that follows the tilt of the motion driving it, with the dead zone marked, next to Invert, Speed and the "driven by" menu. The meter reads the calibrated travel when the device has a calibration and the raw reading over full scale when it does not, and asks for a frame whenever the reading changes. The helpers are back as they are on main, and the calibration state is a badge by the enable toggle.

* 3D mouse: line its preferences up with the rest of Navigation

- The two checkboxes sit side by side in the same two-column grid as Wheel and trackpad.
- Row labels stop repeating "3D mouse" and use sentence case: Calibration, Driver, Restore defaults, Reset view, Left viewport, Right viewport, Left window.
- Hints line up under the control column instead of the left edge.
- The six view axes are named Pan X, Pan Y, Zoom, Orbit X, Orbit Y and Roll so they fit the name column.
- Buttons are a divider and rows in a form like the other groups, without the bold sub-heading.
- The driver, dead zone, axes, picture and buttons sit behind a collapsed Advanced section, since most people never touch them.

* 3D mouse: tidy the Advanced test for clippy

* Timeline: suppressing a feature keeps later features' body names (#4302) (#4311)

* claim: #4302

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

* Timeline: suppressing a feature keeps later features' body names (#4302)

Bodies numbered by the counter (`BodyN`) take the next free number on every
recompute, so suppressing a feature that made one moved every later counted
body down one (`Body2` became `Body1`) and broke what named it, such as a
Move of `Body2`. timeline.suppress now stores the current names of later
features' counted bodies in their `body_names` first, as body.rename already
does for a renamed counted body (#1109). Engine only: no evaluation or
design-format change.

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

* Timeline: a body made while a feature is suppressed keeps its name (#4302)

Suppressing a feature freed its counted name (`Body1`), so a body made
meanwhile took it and became `Body3` once the feature was back. Suppress
now also stores the suppressed feature's own counted names, and the
evaluator adds the names stored on earlier suppressed features to the
state's `reserved_body_names` (the names a rename freed, #4957), which the
`BodyN` counter passes by. They are taken from the design before each
feature is computed and are part of the state digest, so a cached result
is only reused when they match.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* cadmpeg 0.6.3: FreeCAD and Rhino import in the browser too (#4482)

cadmpeg 0.6.3 is pure Rust (ruzstd instead of the zstd C library, zip 9), so
solidcraft-io depends on it on every target and the wasm build reads .FCStd
and .3dm files like the desktop app. The browser's file pickers offer them.

0.6.3 replaces DecodeOptions::container_only with a decode depth; ask for
Geometry, what its STEP writer reads. zip 8 leaves the tree, and with it the
three cargo-deny advisory exceptions kept for it.

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

* Pattern dialogs: Quantity fields get up/down step arrows (#4260) (#4264)

A new kit control, `ui_kit::count_field` / `count_row`, is an expression field for a
whole-number count with stacked step arrows at its right edge. An arrow steps the
evaluated count by one within the field's limits: a plain number is replaced, an
expression keeps its parameters and gains or adjusts a trailing `+ n`. The text
changes as typing changes it, so the live preview follows.

Used by the Quantity rows of the sketch Rectangular (1-500, both directions together
at most 500) and Circular (2-500) patterns, and the solid Rectangular, Circular and
Path patterns (1-1000).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci]

* Join: say so when the joined piece doesn't touch the body (#4434) (#4436)

* claim: #4434

* Join: say so when the joined piece doesn't touch the body (#4434)

A Join (extrude, revolve, sweep, loft, pipe, coil, primitives, thicken,
boundary fill, surface network) whose material meets none of its targets
still joins it to the first target as a separate piece. The result stays
as it was; the feature now warns: "the joined piece doesn't touch `Body1`,
so `Body1` is now in 2 separate pieces (New Body makes it a body of its
own)". History marks it and the command reply carries it.

The check compares pieces (outer shells, not voids) of the bodies the
feature changed or absorbed before and after it, so it holds for the
batched multi-profile path too, stays quiet for a join that bridges
pieces or closes a cavity, and leaves Combine (which joins the bodies it
is given) alone.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Hole: snap to face and round-edge centres, drag placed holes, section in the dialog (#2171) (#2912)

Placing a Single hole put it exactly where the click landed, with nothing to line it up by and no
way to move it but taking it out and picking again; the dialog was a list of size fields.

- Preferences › Design › Create Hole has two settings, both off by default, so a new installation
  places holes as before:
  - Snap to face and edge centres (`hole_snaps`): the planar face under the cursor offers its
    middle and the centres of its round edges (a boss, a rounded corner, a hole already there) as
    marks; a pick near one, or on or beside such an edge, takes that centre and the mark is named.
    Sketch points on the face still come first; Alt or Ctrl held places it where clicked.
  - Move holes by clicking or dragging (`hole_moves`): each placed hole has a handle that drags it
    (snapping when snaps are on) and takes it out when clicked, and a pick elsewhere on its face
    moves it there. Edit Feature shows the handle too, so a hole can be moved later (#1047).
- The Hole dialog draws a section of the hole as its options make it, with each size in a field
  beside the dimension it sets; a hovered or focused field lights its dimension. The layout is a
  new kit element, `ui_kit::figure_rows` (a caller-painted figure beside captioned fields).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Hole: place a Single hole by distances from reference edges (#37) (#2931)

A Single hole can take up to two straight edges of its face as references,
each with the distance of the hole's centre from that edge's line, measured
in the face's plane. `solid.hole` takes them as `references: [{edge, distance}]`;
the edges are found again by their persistent names, else their points, so
the hole follows them when the part changes, and each distance is a
parameter. A bad reference (no edge there, a curved edge, one square to the
face, parallel edges, a negative distance, a centre off the face) is refused.

In the Hole dialog, Reference 1 and 2 follow the face, each with its Distance
row; the canvas shows each distance as a dimension from its edge to the hole,
with a box just past the edge to type it in. Picking an edge measures its
distance (and its box takes the keyboard), typing one moves the hole, and
moving the hole measures them again. Edit Feature picks the edges again with
their distances.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>
Co-authored-by: Frank <58932224+plexi32@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: raykholo <raykholo@users.noreply.github.com>
Co-authored-by: CamWight <91156642+CamWight@users.noreply.github.com>
Co-authored-by: SS-EN <84559909+SS-EN@users.noreply.github.com>
Co-authored-by: Colin Faulkingham <colin.faulkingham@gmail.com>
Co-authored-by: Karl Von Richter <karl@von-richter.net>
Co-authored-by: HiroKenEz <59478073+HiroKenEz@users.noreply.github.com>
Co-authored-by: Peppe Bergqvist <p@bergqvi.st>
Co-authored-by: GitHub Copilot <noreply@github.com>
Co-authored-by: phanoob <lesur.Stephane@wanadoo.fr>
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Kai Evans <nemshilov@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Tyler Chiriboga <tylerchiriboga.apps@gmail.com>
Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: CaRLymx <carlymx@gmail.com>
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
Co-authored-by: Loohi <78334654+Loohi@users.noreply.github.com>
Co-authored-by: leValdemar19 <valdemarnord@hotmail.com>
Co-authored-by: AhmetV <mihendiz10@gmail.com>
Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>
Co-authored-by: rlartot <94693724+rlartot@users.noreply.github.com>
Co-authored-by: Christian Svensson <blue@cmd.nu>
Co-authored-by: Thodoris Tsagkogeorgas <thodoristsagko@gmail.com>
Co-authored-by: ahmetalpcelik <80778880+ahmetalpcelik@users.noreply.github.com>
Co-authored-by: ealitt <ealitt@gmail.com>
Co-authored-by: Homero Thompson <vdirienzo@gmail.com>
Co-authored-by: Diogo Soares Rodrigues <dsrodrigues20@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Artem Sushchev <Suschev.Artem+github@gmail.com>
Co-authored-by: Nathan Dane <nathandane.dev@gmail.com>
Co-authored-by: Kai Evans <hi@kaievans.co>
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: cronos <crono2250@users.noreply.github.com>
Co-authored-by: rbousigues88 <rbousigues@gmail.com>
Co-authored-by: The Nomad <nomad@positron3d.com>
Co-authored-by: Jackson Oursland <jacob.oursland@gmail.com>
Co-authored-by: Jacques <59315319+Jacquesvdberg92@users.noreply.github.com>

* feat(render): reconcile definition-local visual mapping (#1214) (#1223)

Retain the original mapping implementation, all eight analytic/hostile-input tests and mapping contract byte-for-byte from #1223. Reconcile only module registration and the current roadmap on render. Actual current render library 166/0; strict all-target render Clippy, wasm32 and repository gates pass. No persistence, sampler, new capability, kernel/vendor change or external code is added. Historical mixed-base full-CI failures are not relabelled.

* feat(render): add bounded linear film accumulation (#1696) (#1697)

Extract the independent FilmAccumulator from owned donor 3b892eab5e. Keep the existing job cancellation and caller-owned admission/work contracts. PNG resolution, primary camera coverage and progressive consumer wiring remain separate. Add analytic statistics, atomic refusal, allocation/range boundaries and real 4096/1048576-count tests. No kernel, document schema, workflow or capability change.

* Sync main into render and preserve GPU CLI help (#5976)

* CLI: --help on each subcommand prints its usage and exits 0 (#4538) (#4545)

* claim: #4538

* CLI: --help on each subcommand prints its usage and exits 0 (#4538)

Every command now answers `--help` and `-h` with its own usage on stdout and
exit 0, the way the top level has since #1828. Before, each subcommand
answered differently: `fuzz --help` started a fuzz run with the default seeds
and ran until it was stopped, `recipe --help` took the flag for a file name,
and `snapshot`, `render`, `oracle`, `run`, `eval`, `script` and `crashes`
answered with an error on stderr and exit 1.

The help is read before the command runs, so `--help` never starts work: no
fuzz run, no MCP server, no file read.

`fuzz` also refuses an argument it doesn't take, as `snapshot` already did: a
flag it doesn't know and a stray word are errors, not arguments to ignore
while a hundred seeds run.

The usage lives in one place now: each command's lines are a constant, and the
top-level usage is composed from them, so the two cannot drift apart. The
top-level usage is unchanged, byte for byte.

# Conflicts:
#	apps/solidcraft-cli/src/main.rs

* Renaming a parameter follows it into sheet-metal and plastic rules and canvases (#4420) (#4423)

* claim: #4420

* Renaming a parameter follows it into sheet-metal and plastic rules and canvases (#4420)

Document::rename_param rewrote other parameters, feature inputs, dimensions,
configurations and joints, but not the expressions of sheet metal and plastic rules or
the plane a canvas lies on, so after a rename the sheet metal and plastic features
using them failed with "unknown parameter" and the canvas left the view. It renames
them there now, and param_users lists them (a rule's field, a canvas's input), so a
parameter they use can't be deleted.

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

* Parameters: rules' features in drives and the graph; shared rule field lists (#4420)

A sheet metal or plastic rule that uses a parameter makes the sheet metal (or plastic)
features rebuild when it changes, so parameters.users lists them in drives and
parameters.graph links them. SheetRule::fields/fields_mut and PlasticRule::fields_mut
replace the hand-written copies of the rules' fields in params.rs, eval.rs and
sheet.manage_rules. Suggested by @CamWight.

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

* fmt (#4420)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Mirror and pattern: a pattern or mirror feature as the source (#395) (#3998)

* claim: #395

* Mirror and pattern: a pattern or mirror feature as the source (#395)

A pattern or mirror of features refused another pattern or mirror as a
source ("Pat cannot be patterned or mirrored yet"). Such a source now
stands for what it copied: its own sources are replayed at each of its
transforms moved by each of ours, to any depth up to eight. Copies of
a feature picked directly keep the instance numbers they had, so
existing designs keep their face names; copies made through a nested
pattern or mirror are numbered after them, so one feature reached two
ways never names two copies alike. A pattern or mirror of bodies as
the source is still refused.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

* Expression completion: keep longer names visible when the typed word is a unit (#3663) (#3664)

* claim: #3663

* Expression completion: keep longer names listed when the typed word is a unit (#3663)

Typing `rad` into an expression box showed nothing because `rad` is the radians unit and
a word that is itself a known token closed the popup. The popup now stays open on such a
word. A complete name (`width` with `width_extra` defined) opens it unarmed: the longer
names are listed with no row selected, Enter submits what was typed and Tab moves on; Up
or Down picks a row, after which Enter or Tab complete it as before. A unit means
something only after a number, so a unit typed anywhere else (`rad` on the way to
`radius`) is a prefix like any other: the popup is armed and Enter completes `radius`
instead of submitting one radian.

A complete token is extended only by names that start with it (`in` lists `inner`, not
`rounding`), a typed unit offers no other units (`in` does not list `inch`), and a unit
right after a number (`2 in`, the form every dialog starts its sizes in) opens no popup at
all, so nothing sits over a dialog's OK button.

Fixes #3663

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Homero Thompson <vdirienzo@gmail.com>
Co-authored-by: SS-EN <84559909+SS-EN@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Martin Pålsson <martin.palsson@outlook.com>
Co-authored-by: Daniel <daniel.owsianski@gmail.com>

* Render: isolate matched GGX visible-normal sampling (#1654)

Independent numerical constituent of #5493. Part of #1654; the CPU reflection consumer remains #1698, so this does not close its image acceptance. Original sampler, matched PDF and analytic/hostile-input tests are preserved from the reviewed donor. 189 render tests, strict Clippy, wasm32 and repository gates pass; fresh quick run 40086624771 passes. No CI configuration, saved-data or kernel change.

* GPU: match index-matched Fresnel in path and viewport (#6036)

Fixes #6035. Preserve actual GPU/f64 material-oracle checks in both translations. Scoped Vulkan, render, Clippy, WASM and fresh quick checks pass. No CI configuration or gate changes.

* feat(render): expose bounded original appearance recipes (#1722)

* claim: resolve the main/render synchronization conflict for #6050

* Settle appearance panel scrolling before targeting Search (#6079)

* claim: #6076

* test(render): settle appearance scroll before resolving Search (#6076)

Expanded starter swatches moved under the scenario click while the existing scroll animation was active. Advance 20 deterministic harness frames before resolving the Search handle. No wall-clock sleep, timeout change, assertion deletion or production change. Original native reproduction fails at the empty-state assertion; all 124 scenarios now pass with one pre-existing ignored adhoc entry.

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>
Co-authored-by: Victor Santos Oliveira <56417388+Vittuu@users.noreply.github.com>
Co-authored-by: Christian Svensson <blue@cmd.nu>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Codex <noreply@openai.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jackson Oursland <jacob.oursland@gmail.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Frank <58932224+plexi32@users.noreply.github.com>
Co-authored-by: raykholo <raykholo@users.noreply.github.com>
Co-authored-by: CamWight <91156642+CamWight@users.noreply.github.com>
Co-authored-by: SS-EN <84559909+SS-EN@users.noreply.github.com>
Co-authored-by: Colin Faulkingham <colin.faulkingham@gmail.com>
Co-authored-by: Karl Von Richter <karl@von-richter.net>
Co-authored-by: HiroKenEz <59478073+HiroKenEz@users.noreply.github.com>
Co-authored-by: Peppe Bergqvist <p@bergqvi.st>
Co-authored-by: GitHub Copilot <noreply@github.com>
Co-authored-by: phanoob <lesur.Stephane@wanadoo.fr>
Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Kai Evans <nemshilov@gmail.com>
Co-authored-by: Tyler Chiriboga <tylerchiriboga.apps@gmail.com>
Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: CaRLymx <carlymx@gmail.com>
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
Co-authored-by: Loohi <78334654+Loohi@users.noreply.github.com>
Co-authored-by: leValdemar19 <valdemarnord@hotmail.com>
Co-authored-by: AhmetV <mihendiz10@gmail.com>
Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>
Co-authored-by: rlartot <94693724+rlartot@users.noreply.github.com>
Co-authored-by: Thodoris Tsagkogeorgas <thodoristsagko@gmail.com>
Co-authored-by: ahmetalpcelik <80778880+ahmetalpcelik@users.noreply.github.com>
Co-authored-by: ealitt <ealitt@gmail.com>
Co-authored-by: Homero Thompson <vdirienzo@gmail.com>
Co-authored-by: Diogo Soares Rodrigues <dsrodrigues20@gmail.com>
Co-authored-by: Artem Sushchev <Suschev.Artem+github@gmail.com>
Co-authored-by: Nathan Dane <nathandane.dev@gmail.com>
Co-authored-by: Kai Evans <hi@kaievans.co>
Co-authored-by: cronos <crono2250@users.noreply.github.com>
Co-authored-by: rbousigues88 <rbousigues@gmail.com>
Co-authored-by: The Nomad <nomad@positron3d.com>
Co-authored-by: Jacques <59315319+Jacquesvdberg92@users.noreply.github.com>
Co-authored-by: Martin Pålsson <martin.palsson@outlook.com>
Co-authored-by: Daniel <daniel.owsianski@gmail.com>
2026-10-24 14:21:58 +00:00
fugazister a633a67e63 Preferences: choose the window system on Linux (Wayland or X11) (#3083) (#3084)
* claim: #3083

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

* WIP: choose the window system on Linux in Preferences (#3083)

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

* Preferences: a window system set over the control channel is saved for the next start again

The merge kept main's check in ui.prefs, which only looked at the anti-aliasing.

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

* Preferences: the window system section drops the restart badge its note already states

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 12:12:38 +00:00
phanoob 5ca7c2fb95 Sketch tools: pressing a tool's shortcut again cycles its creation modes (#2459) (#3564)
* claim: #2459

* Sketch tools: pressing a tool's shortcut again cycles its creation modes (#2459)

R starts 2-Point Rectangle; R again, before the first corner, switches to
3-Point, Center, Rounded, then back to 2-Point (likewise for Circle,
Line, Arc, Slot, Polygon and Spline keys). A held key's auto-repeat
doesn't cycle, the first press always starts the bound mode, and once a
point is placed the key keeps the tool and its points.

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

* Sketch tools: the mode cycling is a Sketch preference, off by default (#2459)

A tool's shortcut pressed again keeps restarting the tool, as before;
Preferences > Sketch > "Pressing a tool's shortcut again switches its
mode" turns the cycling on.

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:46:46 +00:00
cronos 592f6a6fee ci: fix local scheduling and Windows test baselines (#4941)
* ci: repair local nextest scheduling and Windows fixtures

* test: refresh MSVC robustness references and isolate GNU policy

Integrate the Windows reference records from #3361 and the target-environment
selection and recording policy from #4159. Keep shared records and comparison
tolerances intact, and retain seed 36 as an existing failure tracked by #3583.

Co-authored-by: Ray <raykholo@users.noreply.github.com>
Co-authored-by: chris <christopher.nourse@gmail.com>

* test: make CI subprocess and revolve fixtures deterministic

---------

Co-authored-by: Ray <raykholo@users.noreply.github.com>
Co-authored-by: chris <christopher.nourse@gmail.com>
2026-10-24 11:33:10 +00:00
DuBearte 4612c5e6a9 Equal with three or more curves selected makes them all equal (#4985) (#5139)
* claim: #4985

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

* Equal with three or more curves selected makes them all equal (#4985)

With more than two curves selected, Equal fell back to the pick tool and
applied nothing. sketch.constraint.equal also takes curves: [2..200],
each made equal to the first in one step (strict, so a conflict changes
nothing), and the toolbar sends a selection of three or more curves as
that list.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:26:38 +00:00
DuBearte c0da9fe861 Trim cuts a line at its tangent point on a circle (#5074) (#5163)
* claim: #5074

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

* Trim cuts a line at its tangent point on a circle (#5074)

A line the solver made tangent to a circle touches it within rounding
noise, and the exact intersection missed the touch whenever the noise
fell outside, so trim found no cut there and deleted the whole line.
Trim takes its cut points from intersections_within at its own merge
distance, as profile detection does (#588).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 11:26:36 +00:00
amolinari-autotex 19db763899 Tapped Hole: verified metric fine, UNC/UNF and parallel G/Rp catalogues (#4178)
* feat: expand tapped-hole catalogue with metric fine and Unified sizes

* docs: distinguish metric designations from expanded tapped catalogue

* feat: add internal pipe catalogue and checked Rc profile data

* Connect parallel G and Rp catalogues to cylindrical tapped holes

* fix(i18n): register tapped thread catalogue labels

* fix(i18n): complete thread labels in required locales
2026-10-24 10:22:57 +00:00
Mateusz Sych 9a737bd63c Linked components: joints keep their face after Update Links (#3330) (#4874)
* claim: #3330

* Linked components: joints keep their face after Update Links (#3330)

A linked design comes in as STEP text, which keeps no face names, so the
faces of its bodies were named by their place in the file (`face:<k>`).
An edit of the part that adds a face earlier in the file (a second hole
of the same size) gave that place to another face, and a joint on the
linked part moved there silently.

When a link is made or updated, each imported face is matched to the
part face it was written from (the same geometry), and the Import
feature keeps that face's name in the part (`Feature::link_faces`, a new
pending format change). The linked face is named
`F<import>🔗<digest of that name>`, pieces keeping their qualifiers,
so references follow the part's own history through Update Links as they
do inside the part, and a face the part no longer has is reported.
Linked faces still answer to `face:<k>`, so older references keep
resolving; Update Links warns about joints that use them and reports
joints that no longer solve.

* Linked faces: a solid's twin keeps its own names; renamed faces count as an update

Two solids of a part alike in one place each take the names of the solid
the file names them after. Update Links reports a link whose part named
its faces differently since as updated (a joint may follow that name to
another face), not as unchanged; only a link getting its first names
stays unchanged.

* Linked faces: Extract to File carries the design's face names too

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

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-24 08:33:36 +00:00
Daniel 8b65d41b31 Expression completion: keep longer names visible when the typed word is a unit (#3663) (#3664)
* claim: #3663

* Expression completion: keep longer names listed when the typed word is a unit (#3663)

Typing `rad` into an expression box showed nothing because `rad` is the radians unit and
a word that is itself a known token closed the popup. The popup now stays open on such a
word. A complete name (`width` with `width_extra` defined) opens it unarmed: the longer
names are listed with no row selected, Enter submits what was typed and Tab moves on; Up
or Down picks a row, after which Enter or Tab complete it as before. A unit means
something only after a number, so a unit typed anywhere else (`rad` on the way to
`radius`) is a prefix like any other: the popup is armed and Enter completes `radius`
instead of submitting one radian.

A complete token is extended only by names that start with it (`in` lists `inner`, not
`rounding`), a typed unit offers no other units (`in` does not list `inch`), and a unit
right after a number (`2 in`, the form every dialog starts its sizes in) opens no popup at
all, so nothing sits over a dialog's OK button.

Fixes #3663

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 08:01:48 +00:00
Martin Pålsson 4dc30c092d Mirror and pattern: a pattern or mirror feature as the source (#395) (#3998)
* claim: #395

* Mirror and pattern: a pattern or mirror feature as the source (#395)

A pattern or mirror of features refused another pattern or mirror as a
source ("Pat cannot be patterned or mirrored yet"). Such a source now
stands for what it copied: its own sources are replayed at each of its
transforms moved by each of ours, to any depth up to eight. Copies of
a feature picked directly keep the instance numbers they had, so
existing designs keep their face names; copies made through a nested
pattern or mirror are numbered after them, so one feature reached two
ways never names two copies alike. A pattern or mirror of bodies as
the source is still refused.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 07:55:14 +00:00
SS-EN e67ae66f3e Renaming a parameter follows it into sheet-metal and plastic rules and canvases (#4420) (#4423)
* claim: #4420

* Renaming a parameter follows it into sheet-metal and plastic rules and canvases (#4420)

Document::rename_param rewrote other parameters, feature inputs, dimensions,
configurations and joints, but not the expressions of sheet metal and plastic rules or
the plane a canvas lies on, so after a rename the sheet metal and plastic features
using them failed with "unknown parameter" and the canvas left the view. It renames
them there now, and param_users lists them (a rule's field, a canvas's input), so a
parameter they use can't be deleted.

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

* Parameters: rules' features in drives and the graph; shared rule field lists (#4420)

A sheet metal or plastic rule that uses a parameter makes the sheet metal (or plastic)
features rebuild when it changes, so parameters.users lists them in drives and
parameters.graph links them. SheetRule::fields/fields_mut and PlasticRule::fields_mut
replace the hand-written copies of the rules' fields in params.rs, eval.rs and
sheet.manage_rules. Suggested by @CamWight.

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

* fmt (#4420)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 07:35:31 +00:00
Homero Thompson 5e0e7a4c6a CLI: --help on each subcommand prints its usage and exits 0 (#4538) (#4545)
* claim: #4538

* CLI: --help on each subcommand prints its usage and exits 0 (#4538)

Every command now answers `--help` and `-h` with its own usage on stdout and
exit 0, the way the top level has since #1828. Before, each subcommand
answered differently: `fuzz --help` started a fuzz run with the default seeds
and ran until it was stopped, `recipe --help` took the flag for a file name,
and `snapshot`, `render`, `oracle`, `run`, `eval`, `script` and `crashes`
answered with an error on stderr and exit 1.

The help is read before the command runs, so `--help` never starts work: no
fuzz run, no MCP server, no file read.

`fuzz` also refuses an argument it doesn't take, as `snapshot` already did: a
flag it doesn't know and a stray word are errors, not arguments to ignore
while a hundred seeds run.

The usage lives in one place now: each command's lines are a constant, and the
top-level usage is composed from them, so the two cannot drift apart. The
top-level usage is unchanged, byte for byte.

# Conflicts:
#	apps/solidcraft-cli/src/main.rs
2026-10-24 07:35:25 +00:00
Jacques 807e367bdb Hole: place a Single hole by distances from reference edges (#37) (#2931)
A Single hole can take up to two straight edges of its face as references,
each with the distance of the hole's centre from that edge's line, measured
in the face's plane. `solid.hole` takes them as `references: [{edge, distance}]`;
the edges are found again by their persistent names, else their points, so
the hole follows them when the part changes, and each distance is a
parameter. A bad reference (no edge there, a curved edge, one square to the
face, parallel edges, a negative distance, a centre off the face) is refused.

In the Hole dialog, Reference 1 and 2 follow the face, each with its Distance
row; the canvas shows each distance as a dimension from its edge to the hole,
with a box just past the edge to type it in. Picking an edge measures its
distance (and its box takes the keyboard), typing one moves the hole, and
moving the hole measures them again. Edit Feature picks the edges again with
their distances.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 05:32:30 +00:00
Jacques 400e06c202 Hole: snap to face and round-edge centres, drag placed holes, section in the dialog (#2171) (#2912)
Placing a Single hole put it exactly where the click landed, with nothing to line it up by and no
way to move it but taking it out and picking again; the dialog was a list of size fields.

- Preferences › Design › Create Hole has two settings, both off by default, so a new installation
  places holes as before:
  - Snap to face and edge centres (`hole_snaps`): the planar face under the cursor offers its
    middle and the centres of its round edges (a boss, a rounded corner, a hole already there) as
    marks; a pick near one, or on or beside such an edge, takes that centre and the mark is named.
    Sketch points on the face still come first; Alt or Ctrl held places it where clicked.
  - Move holes by clicking or dragging (`hole_moves`): each placed hole has a handle that drags it
    (snapping when snaps are on) and takes it out when clicked, and a pick elsewhere on its face
    moves it there. Edit Feature shows the handle too, so a hole can be moved later (#1047).
- The Hole dialog draws a section of the hole as its options make it, with each size in a field
  beside the dimension it sets; a hovered or focused field lights its dimension. The layout is a
  new kit element, `ui_kit::figure_rows` (a caller-painted figure beside captioned fields).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 05:25:56 +00:00
Frank 1fa0371c75 Pattern dialogs: Quantity fields get up/down step arrows (#4260) (#4264)
A new kit control, `ui_kit::count_field` / `count_row`, is an expression field for a
whole-number count with stacked step arrows at its right edge. An arrow steps the
evaluated count by one within the field's limits: a plain number is replaced, an
expression keeps its parameters and gains or adjusts a trailing `+ n`. The text
changes as typing changes it, so the live preview follows.

Used by the Quantity rows of the sketch Rectangular (1-500, both directions together
at most 500) and Circular (2-500) patterns, and the solid Rectangular, Circular and
Path patterns (1-1000).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 04:13:39 +00:00
Frank fc3e3b6c8d Join: say so when the joined piece doesn't touch the body (#4434) (#4436)
* claim: #4434

* Join: say so when the joined piece doesn't touch the body (#4434)

A Join (extrude, revolve, sweep, loft, pipe, coil, primitives, thicken,
boundary fill, surface network) whose material meets none of its targets
still joins it to the first target as a separate piece. The result stays
as it was; the feature now warns: "the joined piece doesn't touch `Body1`,
so `Body1` is now in 2 separate pieces (New Body makes it a body of its
own)". History marks it and the command reply carries it.

The check compares pieces (outer shells, not voids) of the bodies the
feature changed or absorbed before and after it, so it holds for the
batched multi-profile path too, stays quiet for a join that bridges
pieces or closes a cavity, and leaves Combine (which joins the bodies it
is given) alone.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 04:13:31 +00:00
github-actions[bot] d1a6262861 Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-24 04:15:11 +00:00
Jackson Oursland 612d9d08dd cadmpeg 0.6.3: FreeCAD and Rhino import in the browser too (#4482)
cadmpeg 0.6.3 is pure Rust (ruzstd instead of the zstd C library, zip 9), so
solidcraft-io depends on it on every target and the wasm build reads .FCStd
and .3dm files like the desktop app. The browser's file pickers offer them.

0.6.3 replaces DecodeOptions::container_only with a decode depth; ask for
Geometry, what its STEP writer reads. zip 8 leaves the tree, and with it the
three cargo-deny advisory exceptions kept for it.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 03:47:00 +00:00
Frank d2eb8cf639 Timeline: suppressing a feature keeps later features' body names (#4302) (#4311)
* claim: #4302

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

* Timeline: suppressing a feature keeps later features' body names (#4302)

Bodies numbered by the counter (`BodyN`) take the next free number on every
recompute, so suppressing a feature that made one moved every later counted
body down one (`Body2` became `Body1`) and broke what named it, such as a
Move of `Body2`. timeline.suppress now stores the current names of later
features' counted bodies in their `body_names` first, as body.rename already
does for a renamed counted body (#1109). Engine only: no evaluation or
design-format change.

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

* Timeline: a body made while a feature is suppressed keeps its name (#4302)

Suppressing a feature freed its counted name (`Body1`), so a body made
meanwhile took it and became `Body3` once the feature was back. Suppress
now also stores the suppressed feature's own counted names, and the
evaluator adds the names stored on earlier suppressed features to the
state's `reserved_body_names` (the names a rename freed, #4957), which the
`BodyN` counter passes by. They are taken from the design before each
feature is computed and are part of the state digest, so a cached result
is only reused when they match.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 03:40:39 +00:00
The Nomad 1f6a2d4daf 3D mouse: driver choice, guided calibration, button mapping and a picture of the device (#1463) (#3825)
* Add a small test boat to steer around with the 3D mouse

A little hull in its own camera, drawn by the UI itself and not the CAD
viewport, so a 3D mouse can be tried out without opening a design. It
brings the model (generated by assets/models/make_test_boat.py) and the
tests for how the camera moves it.

Nothing uses it yet; Preferences picks it up with the rest of the 3D mouse
work.

* UI kit: keep a choice row from flipping with the scrollbar

A choice row picks segments or a dropdown by whether its labels fit. A page
scrollbar that comes and goes narrows the row by its own width, so the row
could flip back and forth between the two on a page that sits right at the
edge. It is now judged on the whole row and through a small helper,
fits_steadily, that only changes its answer once the miss is wider than the
scrollbar.

Also tests for that, for typed numbers in a slider's value box, and a
test hook to put up the issue-report input barrier by hand.

* 3D mouse: device model, guided calibration, buttons and a picture

The 3D mouse becomes a device we know something about instead of six raw
numbers.

The reader picks between spacenavd and the direct hidraw driver, falls back
from one to the other, and says so when it can't open the device. Guided
calibration asks whether the device is a spring cap or a trackball, adds a
touch step, and uses a settle filter so a ball let go of stops reading as
motion. Buttons get jobs (keys, held modifiers, commands), saved per
device, with defaults for the SpaceMouse Pro.

Preferences gets a picture of the device that moves as it does, next to the
test boat, and the same picture can sit in the 3D view at Left Viewport,
Right Viewport or Left Window, above the floating History card like the
legend and readouts.

These ride together because the crate's calibration types, the Preferences
page, the Linux reader and the button hook all use each other.

* 3D mouse: offer to calibrate an uncalibrated device

When a 3D mouse shows up that has not been calibrated, a card in the 3D
view offers to calibrate it, to swap drivers when the other one would work
better, to close, or to never ask again. It is made once per device, goes
away after 30 seconds in view, and gives way to a status or computing
toast instead of stacking on it.

The card is the shared toast, which now takes buttons: they group by
purpose, wrap inside the card at narrow widths, and do nothing while an
issue report holds CAD input. The picture of the device in the 3D view
keeps clear of the card.

* 3D mouse: translations and attribution

The 3D mouse strings (ui.3d_mouse_*, the calibration steps and the offer
card) in every shipped language, the Japanese catalog note, and the credit
for the test boat model in ATTRIBUTION.md.

This is last because the code before it only looks keys up: it builds and
runs without these, falling back to English until they land.

* 3D mouse: draw its preferences with the settings kit like the rest of Navigation

The 3D mouse section was a folded header with its own layout. It now
uses the same kit as the sections above it: a settings_section card,
settings_form rows with the shared label column, dividers between the
device, axes, picture and buttons groups, Text::Hint for the helper
lines, and settings_control_width for the dropdowns.

The section is open like every other one (the header still folds it),
so the device badge on the folded header is gone; the calibration row
already names the device. Every setting, string and key is unchanged.
Tests that unfolded the section no longer click it, and the hint lines
are looked up as hints instead of notes.

* 3D mouse: show each axis's live tilt in Preferences, like main's axis rows

Each view axis row is main's axis row again: a meter that follows the tilt of the motion driving it, with the dead zone marked, next to Invert, Speed and the "driven by" menu. The meter reads the calibrated travel when the device has a calibration and the raw reading over full scale when it does not, and asks for a frame whenever the reading changes. The helpers are back as they are on main, and the calibration state is a badge by the enable toggle.

* 3D mouse: line its preferences up with the rest of Navigation

- The two checkboxes sit side by side in the same two-column grid as Wheel and trackpad.
- Row labels stop repeating "3D mouse" and use sentence case: Calibration, Driver, Restore defaults, Reset view, Left viewport, Right viewport, Left window.
- Hints line up under the control column instead of the left edge.
- The six view axes are named Pan X, Pan Y, Zoom, Orbit X, Orbit Y and Roll so they fit the name column.
- Buttons are a divider and rows in a form like the other groups, without the bold sub-heading.
- The driver, dead zone, axes, picture and buttons sit behind a collapsed Advanced section, since most people never touch them.

* 3D mouse: tidy the Advanced test for clippy
2026-10-24 03:34:21 +00:00
SS-EN 6c6ffe4802 MCP: wait out long requests, honest batch outcomes, batch tool aliases (#2901)
Rebased onto main as one commit (was 120d26f7, df488f9a, db9d1073 plus
merges of main).

- A request the app reports as still running (#1618) is waited for on the
  same connection through `control.pending`; past the wait the error says it
  was not cancelled and will still apply (or only write its file), later calls
  are not sent behind it, and the new read-only `pending` tool returns its
  result once it lands.
- A failed batch says what really happened: `rolled_back` only when the
  design is back where the batch started (as #3288 / #3242 on main: the app's
  `rolled_back` is believed; for an older app, every completed step undone;
  only a rollback asked for is reported as one), plus `applied`, `not_run` and
  `failed.still_running`.
- A batch step may name a tool (set_parameter, list_commands,
  describe_command, execute, measure, inspect_design, undo, redo, export, save,
  open, new_design, crash_reports); screenshot, body_topology, batch and
  pending are refused before anything is sent.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 02:41:41 +00:00
github-actions[bot] 46fca160e1 Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-24 02:34:23 +00:00
rbousigues88 16727f331c docs(mcp): Windows setup for Claude Desktop (#3794)
* docs(mcp): Windows setup for Claude Desktop (paths, two servers, Store config file)

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

* docs(mcp): say the Store build can keep the config elsewhere, not that the other path fails

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:40:56 +00:00
bherbruck 01220d3eb5 Version 0.21.0 on main (released from rc.2's code) (#5774)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 01:05:06 +00:00
Colin Faulkingham 89c1f02850 Sketch slots: provide typed dimension inputs for every variant (#2423) (#4318)
* claim: #2423

* fix(sketch): add typed dimensions to every slot tool (#2423)
2026-10-24 00:59:27 +00:00
cronos 45edd5baf5 Fix midpoint picks in sketch dimensions (#4872)
Retain MID snaps across both picks and materialize reusable midpoint
constraints for point-to-line and point-to-point dimensions. Let midpoint
pairs choose horizontal or vertical spans with pointer placement before
entering a value.

Cover projected model edges, overlapping geometry, helper reuse,
undo/cancel/reopen, and existing angular and line-circle workflows.
2026-10-24 00:58:56 +00:00
Colin Faulkingham 7dd5baac5c Browser: route body picks into open command dialogs (#2560) (#4371)
* claim: #2560

* fix(ui): route Browser body picks into active dialogs
2026-10-24 00:52:31 +00:00
bherbruck 65d288227e Release 0.21.0 (rc.2's code, version only)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 20:51:08 -04:00
Colin Faulkingham 2e5924ed5e Preserve construction references when axes and points are renamed (#4419) (#4449)
* claim: #4419

* fix: preserve renamed construction references (#4419)
2026-10-24 00:24:51 +00:00
Kai Evans a199a2cc25 Section Analysis: OK with no plane picked turns the section off (#2410) (#2411)
While a section is on, Section Analysis opened with no plane picked now
says "No plane picked: OK turns the section off." and OK runs
inspect.section {"clear": true}. With no section on, OK with no plane
still asks for a plane.

Fixes #2410.
2026-10-24 00:11:38 +00:00
Nathan Dane fb374b543b UI: integrate theme radii, borders, palettes and viewport refresh (#5045) (#5046)
* claim: #4922

* UI: let themes own corner radii

* UI: use four-point panel and window radii in SolidCraft 95

* UI: align the 95 radius assertion with four-point panels

* claim: #4953

* UI: let themes own structural and control borders

* UI: single Blueprint outlines and dotted secondary rules

* UI: separate container outlines and add theme-owned 95 bevels

* claim: #5007

* UI: apply theme palettes to icons, workspace tabs and Gruvbox settings

* claim: #5045

* Viewport: invalidate cached geometry colours when the theme palette changes

* UI: preserve legacy theme logo colours with the borderless mark
2026-10-23 23:57:34 +00:00
Karl Von Richter 0dffb206b1 Assembly: ground the first part; drag free joint chains (#2804) (#2805)
* assembly(feat): ground first parts and drag free joint chains

Ground the first top-level part when it is created and let face dragging reposition free occurrences or any member of a floating joint chain in one undo step. Preserve exact repeated-placement frames, joint values, modifier selection and Escape cancellation. Keep the accumulated fixture, viewport layout and documentation integration fixes from the existing PR.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(fix): update movable fixtures for first-part grounding

Accept the documented grounded override in component creation, explicitly free movement fixtures, and keep linked clipboard pastes movable even in an empty document. Cover first and reused linked pastes, default insertion grounding, and single-step undo in the existing link tests.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): keep the motion-cost crank free

Unground the first component in the document-level joint-face measurement fixture so its motion samples can rotate after first-part grounding. Preserve the rotation and shared-measurement assertions that caught the Linux CI failure.

Signed-off-by: Karl Von Richter<karl@embeint.com>

* assembly(test): free the frame placement fixture

Create the frame-member placement fixture ungrounded before moving it above its root sketch. Keep its world-space member assertions while respecting first-part grounding in the frame tests added on main.

Signed-off-by: Karl Von Richter<karl@embeint.com>

---------

Signed-off-by: Karl Von Richter<karl@embeint.com>
2026-10-23 23:15:34 +00:00
Artem Sushchev 30b01e8d79 A spherical shell revolves into a solid with a void, and cuts and joins with hollow tools work (#451) (#4182) 2026-10-23 22:53:42 +00:00
Diogo Soares Rodrigues 1ed394f8d6 Extrude: picking a face, vertex or parallel plane off the profile switches the extent to To Object (#2855)
* Extrude: picking a face, vertex or parallel plane off the profile turns the extent to To Object

With profiles picked, a face off their plane (or curved), a vertex off it, or a
construction plane parallel to it is taken as the To Object target instead of
another profile: the extent switches without the Extent Type choice, and the
distance shows how far the target is. Coplanar faces still join the profiles.

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

* Extrude: automatic To Object is a Preferences setting, off by default

Preferences > Design > "Extrude to a picked target" (`extrude_auto_to_object`, default
false) turns the automatic To Object on. Off, picks in the Extrude dialog only add
profiles and the distance is not synced to a target, as before this change.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
2026-10-23 22:03:23 +00:00
Homero Thompson d5a046c3d3 Agent chat: end the agent's process group where kill is busybox' (#4587) (#4588)
* claim: #4587

* Agent chat: end the agent's process group with the system call (#4587)

The group was signalled with a `kill` program: `kill -s TERM -- -<pgid>`.
That works with procps' `kill` and with BSD's, but busybox' `kill` takes no
`--`: it prints its usage, exits 1 and signals nothing. The agent's helpers
(the CLI an adapter wraps, the MCP servers it starts) then stay alive after
the agent is ended.

The group is signalled with the system call instead, so no `kill` program and
no shell are needed and the behaviour is the same everywhere. `nix` (MIT) is
already in the tree through zip, and only on Unix: the browser build does not
take it.

The two tests that cover this now pass where they failed before:
`agent_chat::process::tests::dropping_the_process_ends_its_group` and
`agent_chat::process::tests::stopping_the_agent_leaves_no_helpers_running`.
2026-10-23 21:25:09 +00:00
raykholo 4353f34bd1 Delete removes a selected construction axis (#4692) (#4695)
* claim: #4692

* Delete removes a selected construction axis (#4692)

Delete on a construction axis skipped it with "the origin axes can't be
deleted". It now deletes the construction axis feature, like construction
planes; the origin axes X, Y and Z are still refused.
2026-10-23 21:25:02 +00:00
bherbruck fedb3bcdb1 Release 0.21.0-rc.2 2026-10-23 17:14:38 -04:00
bherbruck a51dfc9e1f Release 0.21.0-rc.2 (#5691)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:14:35 -04:00
bherbruck 9aaa8016e5 gpu tests: give the two test frames the new hover and selection tints (#5698)
#5109 added Frame::hover_tint and selection_tint; two test frames that landed alongside it didn't set them, so solidcraft-gpu's tests stopped compiling on main.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 17:04:32 -04:00
Colin Faulkingham 71f7a8e697 Move: scope box selection to the active component (#4459) (#4527)
* claim: #4459

* fix: scope Move box selection to active component (#4459)
2026-10-23 20:25:00 +00:00
bherbruck 632ec4157c Sketch on a picked origin plane uses the plane shown after standing an import up (#5659)
* Sketch on a picked origin plane uses the plane shown, also in a moved component

After a STEP file opens its component stays the active one, and standing it up
turns that component a quarter about X. Starting a sketch on XY picked in the
viewport then sent `{"plane": "XY"}`, which names the active component's own
XY: upright after the stand-up. The sketch landed on the world XZ, Look At
turned to face Y and the extrude went along Y.

The viewport and browser only show the design's origin planes, so a picked
origin plane now goes to sketch.create written out as that world plane while
the active component is moved; sketch.create maps it into the component's
frame once. The flip toward the viewer uses the same plane.

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

* Stand-up axes tests: stand the file up through the offer when one is waiting

#5655 offers to stand up SolidWorks files instead of turning them silently.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:11:39 -04:00
ealitt 6eaf723041 fix(ui): preview shortcut chords and wait for complete modifier input (#1810)
Keep physical modifier events out of the ordinary-key slot, preview held keys immediately, and confirm after release and a one-second settling interval. Preserve legacy control-channel modifier behavior while allowing explicit physical Control/Command input. Add regression coverage, a UI scenario, localized guidance and documentation.

Fixes #1809.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 20:05:58 +00:00
bherbruck c3ed8b1c07 Offer to stand up SolidWorks files instead of turning them silently; add Turn Up Axis to the component menu (#5655)
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 16:05:46 -04:00
Jackson Oursland 8301d1c48e Viewport: whole-body highlights reuse the model's own triangles (#4554) (#5109)
Hovering or selecting whole bodies -- a browser row, a History group, a body
under the pointer in body-pick mode -- built a CPU copy of every body's
triangles into the highlight scene. On a 40-body 4.3 MB STEP assembly that was
147 bodies, 70 MB of triangle copies and a 519 ms frame.

The model already has the same geometry on the GPU. The highlight now names it
and the renderer draws it again in the highlight colour:

- bodies drawn from a display plan: the model's evaluated plan chunks, with a
  hover/selection tint uniform (the chunks stay resident in the model scene);
- bodies drawn as placements of shared triangles: the same placements of the
  model's shared triangles, coloured per instance;
- bodies the model builds on the CPU: their vertex range in the model scene's
  block.

See-through and faded bodies keep the CPU feedback, which colours them; faces
and edges highlighted on their own are unchanged. The highlight looks the same
(verified side by side).

On the assembly the highlight build drops from 519 ms to 9 ms and no longer
copies any triangles; selecting the whole assembly takes 9-20 ms.

Co-authored-by: Claude Code <noreply@anthropic.com>
2026-10-23 16:05:41 -04:00
github-actions[bot] 2a02eb71ac Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-23 20:04:02 +00:00
raykholo bb155d446a Tests: moving an occurrence recomputes only the cuts and joins it touches (#4965) (#4982)
* claim: #4965

* Joins and cuts follow only the placements they depend on (#4965)

Tests that turning a joint between parts cuts no hole again, and that a
tool cutting another part cuts again when that part moves relative to it,
while an unrelated part moving keeps the result.

Booleans and Combine now hash only the placements of the bodies they take
as inputs, relative to the frame that evaluates them (#3397), so these
pin that behaviour for joins and cuts with named targets across parts.
2026-10-23 17:22:24 +00:00
bherbruck 98c4f335e8 Control channel and MCP on by default, with a setting to turn it off (#5561)
The app opens its localhost control channel on port 27878 (or the next free
one) unless Preferences > Assistant turns it off; --control and
SOLIDCRAFT_CONTROL_PORT still win. A current.port discovery file lets
solidcraft-cli mcp find the running app with no arguments.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 16:00:35 +00:00
ahmetalpcelik 4bdeaacde6 Crash reports: keep component.activate in the journal (#5079) (#5121)
* claim: #5079

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

* Crash journal keeps component.activate (#5079)

Activating a component changes only the session's active component and
occurrence path, not the design, so the journal dropped the command and a
crash report's replay built later parts under the previously active
component. The journal now also keeps a command that changes either.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:35:54 +00:00
raykholo e5dffc7052 Flatpak: find the host's fonts for CJK text and the font list (#5124) (#5127)
* claim: #5124

* Flatpak: find the host's fonts for CJK text and the font list (#5124)

Inside the Flatpak sandbox the host's fonts are mounted at /run/host/fonts
and /run/host/user-fonts, which no font search looked at. Japanese and
Chinese text fell back to boxes, and the font list missed the host's
families.

- Search both folders for the CJK and Chinese fallbacks and for the
  installed-font list.
- The CJK fallback reads SOLIDCRAFT_FONT_DIRS (a path list, as the font
  list already does), with CRAFT_FONTS_DIR kept as a fallback.
2026-10-23 15:18:58 +00:00
Thodoris Tsagkogeorgas 43fa25458d Line tool snaps a diameter through the circle centre (#4326) (#4327)
A line drawn across a circle only snapped its end onto the curve, so a
diameter could miss the centre at any angle. Near the centre the line now
locks onto the radial line: on the rim it stays on the circle and the centre
is kept on the line, and on the centre point the end lands there.
2026-10-23 14:55:39 +00:00
phanoob 9b8fa3d357 Browser: a body picked in the viewport is opened to and scrolled to (#3005) (#3081)
* claim: #3005

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

* Browser: a body picked in the viewport is shown in the Browser (#3005)

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

* Merge main; viewport::select takes a toggle flag now

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:48:11 +00:00
raykholo 755ad9af07 Dragging a part along its joint no longer highlights what's under the pointer (#5137) (#5140)
* claim: #5137

* Dragging a part along its joint no longer highlights what's under the pointer (#5137)

The viewport's hover pick was skipped during a box selection and camera
moves, but not while a jointed part was dragged, so faces and edges under
the pointer lit up on every frame of the drag. The pick now waits until
the joint drag ends.
2026-10-23 14:45:45 +00:00
Christian Svensson 46aa3d5d47 STEP import: let a cancelled import release the import slot (#3171) (#3189)
* claim: #3171

* Release cancelled file import slots (#3171)
2026-10-23 14:31:51 +00:00
rlartot 125b8d5387 Return to the originating tab after finishing a sketch (#3250) (#3264)
* claim: #3250

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

* Return to the originating toolbar tab after finishing a sketch (#3250)

A sketch started from SHEET METAL or PLASTIC always ended on the SOLID
tab. Remember the tab that was active when the sketch began
(UiState::sketch_return_tab, not saved) and go back to it when the
sketch ends. Falls back to SOLID when that is not one of the normal
toolbar tabs. A sketch started from SOLID behaves as before.

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

* Test: no one-element loop in the return-tab test (clippy single_element_loop)

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 14:31:48 +00:00
phanoob b5edd98bc9 Parameter import and image pickers offer their own file types (#3216) (#3314)
* claim: #3216

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

* Parameter import and canvas image pickers offer their file types (#3216)

Both opened the picker with no file type of their own, so it listed only
designs, CAD files and meshes (and All files). macOS lets you pick only
the types a filter names: CSV parameters, and PNG or JPEG images for a
canvas, were greyed out. They now ask for csv/json and png/jpg/jpeg,
which the picker lists first (#1801).

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:31:46 +00:00
Victor Santos Oliveira 647a52d0fb Close Change Parameters with Escape (#3372) (#3399)
* claim: #3372

* Fix Escape cancellation in Change Parameters

Consume Escape before parameter editors can commit pending text on focus loss, while preserving autocomplete and shortcut capture priority. Add 11 UI regressions covering draft cancellation, preview cleanup, document and history preservation, and reopening saved values.
2026-10-23 14:31:42 +00:00
github-actions[bot] 8c977131d9 Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-24 00:20:18 +00:00
AhmetV 30a1fe74ca Viewport: right-click keeps a multi-selection of faces and edges (#3733) (#3788)
* claim: #3733

* Viewport: right-click keeps a multi-selection of faces and edges (#3733)

---------

Co-authored-by: AhmetV <344941595+ahmetv10@users.noreply.github.com>
2026-10-23 14:31:30 +00:00
Tyler Chiriboga fa2c1c31f6 3MF import: name a Bambu Studio project's bodies after its objects (#3858) (#3859)
* claim: #3858 (3MF import: names from a Bambu Studio project)

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

* 3MF import: name a Bambu Studio project's bodies after its objects (#3858)

Bambu Studio leaves the name off the root model's objects and keeps it in
Metadata/model_settings.config (<config><object id="N"><metadata key="name">),
so every object of a project came in as Mesh1, Mesh2, ... A build item whose
root object has no name of its own now takes the object's name from those
settings. A name in the model still comes first, and part names, other
settings and objects of other model parts are left alone.

The settings are read only when a body needs a name, within what the model
parts left of the 1 GB budget. Settings that can't be read (malformed, nested
too deeply, too large) never fail the import: the bodies keep the generic
names, with a warning saying why.

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

---------

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:31:27 +00:00
leValdemar19 7e472bc4da Cancel Sketch: leave a sketch without keeping its changes (#3838) (#3938)
* claim: #3838

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

* Cancel Sketch: leave a sketch without keeping its changes (#3838)

sketch.cancel (Cancel Sketch, ribbon Exit group and the sketch's
right-click menu) puts the design back as it was when the open sketch
was created or opened for editing: a new sketch is removed with its
dimension parameters, an edited one returns to how it was. It is one
undo step, so Undo goes back into the sketch.

The session remembers the design and the undo history at the point a
sketch is entered. Cancel uses that only while the history still runs
through it (the same steps before, every step since taken in this
sketch); otherwise it refuses and points to Undo, so a stale entry can
never throw away later work.

The icon is the approved close glyph in the sketch accent (alias
sketch_cancel); ja and zh carry the new label.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:31:22 +00:00
Loohi 145c6ad60d Rigid joint saved with the wrong occurrence when two occurrences share a component (#3857) (#3939)
* claim: #3857

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

* Joint dialog: a body of a further instance picks that occurrence, not the component's first (#3857)

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

---------

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 14:31:20 +00:00
raykholo e1bf784014 Deleting an inserted file removes its components, placements and joints (#4100) (#4113)
* claim: #4100

* Deleting an inserted file removes its components, placements and joints (#4100)

A STEP or mesh file inserted into a design makes a component tree. Deleting
its feature left that tree behind: the components, their occurrences, the
rigid joints that hold them and the bodies' component assignments. A later
body that took an imported body's name was then placed in the stale component.

Document::remove_component_tree removes a component subtree and everything
that refers to it: the features made inside it, its occurrences, the joints
on them with their motion links, joint origins, contact set members, motion
study keys and exploded moves, body component and offset entries, standard
part and link records, and configuration columns. Deleting an inserted
file's feature and component.delete both use it; component.delete now also
removes joints left pointing at a deleted occurrence. The body transfer
refusal moves with it, unchanged.

* Only an inserted file's feature takes its component tree when deleted

Mark the feature a STEP, IGES or mesh insert makes (`inserted`), and remove
the component tree on delete only for those and for a linked design's
feature. Deleting a copied component's independent base deletes just that
feature, as before. Older designs get the mark as they open (pending format
change), except copied components' independent bases.
2026-10-23 14:31:08 +00:00
Colin Faulkingham 1bbc5ab0f7 Keep long Select Other labels inside the popup (#4209) (#4225)
* claim: #4209

* fix(ui): bound Select Other labels and expose full names (#4209)
2026-10-23 14:30:57 +00:00
raykholo c1e9d2f057 Esc ends the navigation bar's Pan, Orbit and Zoom modes (#4226) (#4233)
* claim: #4226

* Esc ends the navigation bar's Pan, Orbit and Zoom modes (#4226)

The nav bar toggled a mode on, but nothing cleared it on Esc, so a left drag
kept panning, orbiting or zooming. Esc now ends an active nav mode first;
the next Esc reaches the tool or dialog as before.
2026-10-23 14:30:54 +00:00
CaRLymx 68abb15d7f Spanish (es-ES): catch up with en.json (#4292)
Co-authored-by: carlymx <24634598+carlymx@users.noreply.github.com>
2026-10-23 14:30:51 +00:00
Tyler Chiriboga 945e6261cc 3D mouse test: clear the frame's texture changes, so it passes in debug builds (#4382)
each_frame_hands_the_view_its_motion_through_the_drivers_mapping dropped
the output of a frame it ran (`let _ = ctx.run_ui(...)`). epaint panics
in debug builds when a TexturesDelta is dropped with deltas left ("Dropped
TexturesDelta with 1 unapplied deltas"), so the test failed under a plain
`cargo test` on every platform while the ci profile passed. It now clears
them, as the other UI tests do since #60.

Co-authored-by: Tyler Chiriboga <18270301+Tyler-Chiriboga@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:30:38 +00:00
raykholo d6b1608f9d "Don't Save" looks like a button in the Save changes prompt (#4380) (#4400)
* claim: #4380

* "Don't Save" looks like a button in the Save changes prompt (#4380)

The prompt's third action used the kit's low-emphasis text style, so it
didn't read as clickable and people thought Cancel was the only way out.
Add an opt-in secondary_left style to the confirm footer (a framed
secondary button from the theme tokens) and use it for Don't Save.

* Save prompt test: clear each frame's texture delta once it is applied

egui 0.36 panics when a TexturesDelta with unapplied deltas is dropped;
the test kept only the last frame's output and dropped the earlier ones.
2026-10-23 14:30:32 +00:00
raykholo 2d54cb31bc Flat Pattern view draws perforations (#4440) (#4441)
* claim: #4440

* Flat Pattern view: draw perforation holes (#4440)

The flat view drew the outline, cut-outs, pockets and bend lines but not
perforation fields, though the flat-pattern data and the DXF export list
their holes. The view now reads the hole centres from the sheet through
SheetBody::perforation_holes, caches them per model state and body, and
draws them as circles in the cut-out style. Holes outside the viewport or
smaller than one pixel are skipped, and the cache is capped at one million
holes.
2026-10-23 14:30:27 +00:00
Frank 3acb81964a Sketch offset keeps its source lines free to turn (#4464) (#4466)
* claim: #4464

* Sketch offset keeps its source lines free to turn (#4464)

An offset adds a Parallel between each source line and its offset line.
Since offsets became associative, the offset's points are held during a
solve, so each Parallel pinned its source line to the offset line's
current direction: a closed chain lost its turning freedom (8 degrees of
freedom became 6), and Horizontal or an angle edit on a source line was
refused as conflicting.

The solver and the constructive reading now leave out a Parallel that
ties an offset line to the source it follows while the association lives;
the association already keeps them parallel. After Break the stored
Parallel drives again.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:30:21 +00:00
raykholo 4e7bf2b63a Toolbar: a constraint dragged out of the small box becomes a full button (#5254) (#5456)
* claim: #5254

* Toolbar: a constraint dragged out of the small box becomes a full button (#5254)

The Constrain panel's small box of constraint icons was a fixed list, so
dragging one onto the full-size buttons saved a new order but it was still
drawn in the box. The toolbar settings now remember, per tab, which box
commands were moved out; those are drawn as full buttons, the box closes
up, and dropping one back on a box icon returns it. Reset Toolbar restores
the default box, and settings saved before this load unchanged.
2026-10-23 14:28:23 +00:00
Kai Evans 3093ce1d87 Renaming a body keeps the evaluated timeline, off the window thread (#2467) (#2468)
* Renaming a body that later features change evaluates nothing (#2467)

Model::rename_body now keeps the results when later features change the
body (a pattern, a chamfer, a join). It still evaluates when a body of
that name comes and goes or isn't the same body throughout, when a later
body's name is worked out from it (X (2)), or when a message quotes it.

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

* Rename box: rename off the window thread, and locked bodies follow a rename (#2467)

The rename box commits body.rename through run_heavy, so a rename that
still evaluates later features (one names the body in its targets)
computes beside the window instead of freezing it. Locked bodies now
follow a body.rename from anywhere (the rename box, the control channel,
scripts), not only the rename box.

---------

Co-authored-by: bherbruck <brennen.herbruck@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:23:13 +00:00
phanoob ca9dd02201 Reorder design tabs by dragging them (#3187) (#3188)
* claim: #3187

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

* Reorder design tabs by dragging them (#3187)

A tab dragged along the strip follows the pointer while the others make
room, and lands where it is released; Escape, or releasing down the
window, leaves the order as it was. With the custom title bar a drag that
starts mostly up or down (or on the only tab) still moves the window.
The control channel's ui.documents gains a move action (from, to).

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:23:04 +00:00
raykholo 1d9bdb1ca3 FreeCAD/Rhino import: decode caps with room for detailed parts (#1783) (#3209)
* claim: #1783

* FreeCAD/Rhino import: decode caps with room for detailed parts (#1783)

cadmpeg decoded FreeCAD and Rhino files under its service profile, whose
million collection items and 2.56e8 work units a detailed part (a sprocket,
a threaded rod, a part with a few hundred faces) runs past: the import was
refused with "resource limit on CollectionItems: BudgetExceeded". Allow
8 million items and 2e9 work units, half of cadmpeg's desktop profile; the
input-size cap stays SolidCraft's STEP limit and every other cap stays at
the service profile's.

decoding_is_capped checks the two raised caps (and that they stay below the
desktop profile's) and that every other cap is still the service one.

Fixes #1783
2026-10-23 14:23:02 +00:00
github-actions[bot] d78494e016 Regenerate THIRD-PARTY-LICENSES.txt and docs/features.md [skip ci] 2026-10-23 22:44:53 +00:00
phanoob d18cc0df4e 2-point circle takes a typed diameter (#3278) (#3335)
* claim: #3278

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

* 2-point circle takes a typed diameter (#3278)

After its first point the 2-point circle tool showed no value box, so the
circle could only be sized with the cursor. It now shows a Diameter box,
like the centre circle: the typed value puts the second point that far
along the cursor's direction, and Enter draws the circle.

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

---------

Co-authored-by: Phanoob <phanoob@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:22:59 +00:00
Peppe Bergqvist 04dc68d988 macOS: Fusion navigation preset does not support Shift + two-finger orbit (#3446)
* Macos: keep touchpad/key modifiers true to fusion

* fix: remove obsolete orbit snap reset from trackpad navigation

Upstream removed ViewportState::orbit_snap. The new trackpad orbit path retained one reference, causing E0609 in the PR merge build.

Validation: all seven trackpad tests pass; cargo check -p solidcraft-ui-egui --all-targets passes on the exact failing CI merge 66a2791b with this fix applied; rustfmt and diff checks pass.

Co-Authored-By: GitHub Copilot <noreply@github.com>

* fix: retain Fusion trackpad help in refactored preferences

Preserve upstream's new preferences pages and apply the translated Fusion trackpad tooltip in prefs/navigation.rs.

Validation: cargo build -p solidcraft passed. Tests and full CI were not run, as requested.

Co-Authored-By: GitHub Copilot <noreply@github.com>

* style: format Fusion trackpad preferences tooltip

Apply rustfmt's required wrapping for the relocated tooltip. No behavior changes.

Validation: cargo fmt --all -- --check and git diff --check passed.

Co-Authored-By: GitHub Copilot <noreply@github.com>

---------

Co-authored-by: GitHub Copilot <noreply@github.com>
2026-10-23 14:22:57 +00:00
HiroKenEz efb45d7411 Delete in a rolled-back history warns about the features after the marker (#3471) (#3698)
* claim: #3471

* Delete in a rolled-back history warns about the features after the marker (#3471)

With the timeline rolled back, the checks a delete runs first (the
confirmation's "these features will fail", timeline.dependents and
selection.delete's would_fail) compared models evaluated only up to the
marker. Features after it, broken by the delete, were not listed, so their
error marks turned up only once the timeline moved to the end.

newly_broken_in_history evaluates both sides over the whole history when
either has a marker (on copies; the model and document are unchanged) and is
used by the delete's report and by timeline.dependents.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:22:52 +00:00
raykholo 426f8ddd18 Rectangular and circular pattern: copies that miss the body are reported (#3042) (#3919)
* claim: #3042

* Warn when cut pattern copies remove no material

A rectangular or circular pattern whose copies miss the body succeeded silently and those copies did nothing. The pattern now reports how many of its cut copies removed no material, and keeps the copies that land.
2026-10-23 14:22:45 +00:00
Karl Von Richter 27d2fe6ec0 Frames: profile library, members along frame sketch lines, and the Frame Member dialog (#4003) (#4004)
* claim: #4003

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

* Frames: the profile library (#4003)

The first slice of frames and weldments: the cross-sections structural members
will be made of, each SolidCraft's own parametric definition built in code
(no vendor or standard size tables are shipped).

- solidcraft_doc::frame: bar, round bar, rectangular and round tube, angle,
  channel, I-beam and a generic T-slot extrusion as exact line/arc regions,
  with defaults worked out from the profile's size, refusals that name the
  dimension that does not fit, anchors and placement, and exact section
  properties (area, centroid, Ixx/Iyy/Ixy by Green's theorem).
- frame.profile.list, frame.profile.properties (with mass per metre for a
  density) and frame.profile.insert (draws a profile into a sketch, locked by
  default, at a point, anchor and angle).
- Tests against hand-computed values (rectangles, circles, composite
  rectangles by the parallel-axis theorem, fillet spandrels), hostile input,
  extrusion volume, undo; catalog entries in every locale; docs/frames.md;
  ROADMAP M15.

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

* Frames: drop the built-in T-slot profile (#4003)

Aluminium extrusions and other vendor sections come from the user's own
sketch (drawn or inserted from the vendor's DXF), so the library keeps the
plain steel shapes only.

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

* Frames: members along frame sketch lines (#4003)

frame.member lays one body per straight line of a frame sketch (2D lines or
3D sketch lines), its cross-section square to the line from end to end:

- profile: a built-in profile, or profile_sketch, a sketch whose material
  (nested loops alternating, so two circles make a tube) is the section in
  its own x and y around its origin; profiles picks override it.
- anchor (origin, centre, centroid or a bounding-box corner or edge middle),
  angle (an angle expression, so a parameter), mirror.
- Orientation: the profile's y as near world up as the line allows, x
  square to both; a vertical member's x is world X (frame::member_axes).
- Members follow sketch and parameter edits, undo as one step, save and
  reopen (format: pending/frame-members.rs), and are named after the profile
  (Rect Tube 40x40x3, (2)…), numbering on past names already taken.

Tests against hand-computed volumes and bounding boxes (rectangle, 3D
tower lines, parameter edit, sketch tube profile and picks, anchor/angle/
mirror), refusals, undo/redo and reopen; walk fixtures and goldens; docs.

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

* Frames: fixes from the adversarial review (#4003)

- Default member lines are what was drawn: projected and other linked curves
  and 3D wires (a face sketch's edges) and lines of no length are left out.
- A profile sketch's material starts from its drawn profiles, as
  solid.extrude's "all" does, so a section sketched on a face works; the
  even-odd test uses outlines and boxes worked out once, capped at 256.
- Member names are pinned to their lines (`names`, by line id; `body_name`
  is the base): deleting or adding a line, or deleting another feature, no
  longer renames other members, and frame.member stores every name it gives.
  body.rename renames a member on its line (also for later member features);
  a copy whose body_names were set still names its members by position.
- Built-in profile dimensions are expressions (ProfileSpec): they follow
  parameters, bare numbers take the design's units, and they are feature
  inputs. frame.profile.properties/insert read them the same way.
- Default root/toe radii shrink to the room a thick section leaves; radii
  given are checked by name; a radius must be 0 or at least 0.01 mm; error
  messages report the wall or flange value given, not twice it.
- A line within 1e-6 rad of vertical orients as vertical, so rounding never
  turns a post a quarter turn.
- A 2D frame sketch in a differently placed component is mapped into the
  member's frame, and member features recompute when placements change.
- Caps before lookups for a design's `lines`; a line listed twice is
  refused; a 3D wire that runs back on itself is not a line.

Regression tests for each, from the review's scenarios.

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

* Frames: the Frame Member dialog on the ribbon (#4003)

- Frame Member (MODEL > Create) opens a dialog: the frame sketch's lines
  picked in the view (or All Lines), a built-in profile and its dimensions or
  a section picked in another sketch, anchor, angle, mirror and name, with a
  live preview. Edit Feature reopens it filled in.
- Lines picked in the view keep their sketch (a SKETCH_LINES input picks
  ExternalSketchCurve): line ids are each sketch's own, so two 3D sketches
  both have a `w2`, and the members went on the newer sketch's lines.
- timeline.redefine takes frame.member. Members keep their names through an
  edit unless it gives them a new body_name; frame.member takes `names`.
- A member whose section is a whole profile sketch keeps it when edited
  (the dialog shows the sketch instead of picking every region, which would
  fill a tube's hole).
- An original frame_member icon, open for review, on the ribbon, timeline
  and browser.
- The angle profile is listed as L-Angle, and web and flange as Web/Flange
  thickness, so they translate apart from the existing Angle, Web and Flange;
  the dialog's labels are in en, ja and zh. en-GB no longer repeats the frame
  command labels it spells the same.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:22:41 +00:00
raykholo 6fb72c7401 ui-egui tests: clear the texture deltas of the frames they run (#4039)
Nine tests dropped an egui frame's output with its texture delta still
unapplied (the expression completion editor's frames, the slot preview's
three, the browser's feature rows). epaint asserts against that in debug
builds, so they failed under the dev profile and passed only where debug
assertions are off. They clear the delta as the other tests do; one more
hover test that dropped its output the same way does too.
2026-10-23 14:22:37 +00:00
raykholo 14ccc71291 Fix feature editor picks after upstream sketch resizing (#4083) (#4084)
* claim: #4083

* Feature editors refill named edges and faces where they are now (#4083)

After an upstream sketch change, Fillet, Chamfer, Shell and Draft evaluated
correctly, but their editors refilled the originally clicked points and
could then fail with "no edge at ...". Resolve the stored names through the
document's naming helpers first, keep the resolved index, and map the pick
into the displayed body's frame; the point lookup stays as the fallback.
2026-10-23 14:22:35 +00:00
raykholo ec56df3ffd Preserve expression-positioned sketch fillet corners (#4097) (#4106)
* claim: #4097

* Sketch fillet keeps a corner placed by an expression (#4097)

Filleting a corner whose position comes from parameter expressions deleted
the point and its expressions, so the sketch gained two degrees of freedom
and later parameter changes distorted the outline. keep_corner now also
keeps fixed or expression-positioned points as virtual sharp corners.
2026-10-23 14:22:33 +00:00
CamWight 8565f3f1b3 Browser: a second click on a Document Settings entry closes its window (#4150) (#4152)
* claim: #4150

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

* Browser: a second click on a Document Settings entry closes its window (#4150)

Each entry under Document Settings only opened its window. A second click on
Units closed the menu on the press (the row is outside it) and opened it again
on the release; Precision left Preferences open; Parameters and Design
Properties came back as a fresh copy. Now a click on an entry whose window is
open closes it the way the window's own x does:
- Units: context_menu::toggle_for with the row as the opener, the fix #1522
  made for the workspace button's menu. A right-click still opens it at the
  pointer.
- Precision: prefs::close(app, true), the Cancel/x path that puts back what
  changed since the window opened (now pub(crate)).
- Parameters and Design Properties: the window is dropped, as its x does. A
  Parameters value typed but not applied yet is dropped with it, as today's
  second click already drops it.

Fixes #4150

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:22:29 +00:00
Colin Faulkingham 12c823ffdd Browser: add Rename and Delete to feature menus (#3839, #4343) (#4342)
* claim: #3839

* fix(ui): add Rename and Delete to browser feature menus
2026-10-23 14:22:17 +00:00
SS-EN 5d92ad1e23 Renaming a plane keeps its projections, canvases and hidden state (#4353) (#4354)
* claim: #4353

* Renaming a plane keeps its projections, canvases and hidden state (#4353)

timeline.rename only renamed the feature in other features' references. A sketch's
projection of a construction plane, a canvas placed on it and the hidden list keep it
by name too, so they lost it: the sketch warned that the plane no longer exists, the
canvas left the view and a hidden plane showed again. Document::rename_feature_keys
renames it in sketch links and canvases, as rename_body_keys does for bodies; the
selection follows in the command, and the hidden list in Session::refresh_from, so a
hidden plane stays hidden through undo and redo too.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:22:13 +00:00
CamWight e8a6a5d3ff DXF: write and read a Bézier at its own degree (#5269) (#5281)
* claim: #5269

* DXF: write and read a Bézier at its own degree (#5269)

The DXF writer and reader held a spline's degree to 7, the value MAX_BEZIER_DEGREE had before
#1642 raised it to 9. A G4 blend (one Bézier of degree 9) was written as a degree-7 SPLINE over
its first 8 control points, so a flat pattern's cut-out stopped 4.444 mm short in the file.

- seg_shape takes its limit from MAX_BEZIER_DEGREE: a Bézier is written whole, at its own degree.
- read_dxf reads SPLINE degrees 1 to MAX_BEZIER_DEGREE, the degrees a sketch spline takes.
- Tests: every Bézier degree 4 to 9, a degree-9 round trip, the reader's degrees, hostile SPLINE
  degrees and too few control points, the G4 cut-out exported and read back with its areas, and a
  degree-9 SPLINE with 2 to 10 points inserted into a sketch.

Fixes #5269

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:20:12 +00:00
raykholo 605d32ceb0 ui-egui tests: offscreen GPU tests start their devices one at a time (#3989) (#3990)
* claim: #3989

* ui-egui tests: offscreen GPU tests start their devices one at a time

Every offscreen test started a wgpu instance of its own, and starting one
makes the Vulkan loader scan its drivers. With several test threads two
tests doing that at once crashed the test process with SIGSEGV inside the
loader (loader_icd_scan). offscreen_device now holds a process-wide lock
while it starts the instance, adapter and device; each test keeps its own
device and the rendering still runs side by side.
2026-10-23 14:17:30 +00:00
Frank b79c03d227 Viewport: show reference geometry through bodies (Preferences, off by default) (#2697)
A new Graphics preference, "Show reference geometry through bodies", draws
inactive sketch contours, origin and construction planes and their axes over
shaded bodies. It is off by default, so the viewport looks as before: reference
geometry is depth-tested and a section leaves it whole (#1229); Create Sketch
and dialogs picking a plane still draw the planes through the model.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:14:48 +00:00
raykholo 17bac141c8 An intersect that would leave nothing is refused instead of deleting the body (#5295) (#5303)
* claim: #5295

* An empty intersect is refused instead of deleting the body (#5295)

An intersect whose boolean came out empty removed the target body and
reported success. The shared evaluator now refuses it when nothing of a
named target would remain, when nothing of any target would remain, or
when the tools meet a target and the result is still empty. An unnamed
target the tools miss altogether is still removed, as an intersect with
every body does (#5160). Sheet metal flat machining follows the same rule.

* Sheet metal: an intersect with already removed material is refused (#5295)
2026-10-23 13:50:47 +00:00
gsrunion 4b42ef50c7 Mesh leftovers from the ribbon redesign (#133) (#4471)
* claim: #133

* mesh: Save As Mesh declares the panel that shows it (#133)

The ribbon redesign (#2176) removed the MESH tab, but three things still
pointed at it:

- `file.save_mesh` declared `.at("MESH", "MODIFY")`. The command inventory
  prints the placement a spec declares, so `docs/features.md` advertised a
  `MESH > MODIFY` location no ribbon has. Its real home is already
  INSERT / FILES (`SECTIONS` lists it), so the declaration now agrees with
  the ribbon; `destination()` resolved it there before and after, so nothing
  moves on screen.
- The user guide sent people to "the Mesh tab under Modify" for Save as mesh.
- A test now refuses any command that declares a tab the ribbon removed, and
  pins where Save As Mesh lives.

`PLACEMENTS` keeps its MESH entries: those migrate toolbars users saved under
the old panels, which is a different question from what a spec declares.

---------

Co-authored-by: Geraldrunion <geraldrunion@users.noreply.github.com>
2026-10-23 13:45:35 +00:00
Peppe Bergqvist fdde3f9da9 Add macOS native menu bar (#2361) (#4531)
* claim: #2361

Co-Authored-By: GitHub Copilot <noreply@github.com>

* feat: add native macOS menu bar (#2361)

Reuse document actions and unsaved quit handling in native application, File, Edit, Window and Help menus. Replace duplicate header controls only when native installation succeeds, preserving other platforms and the fallback UI.

Co-Authored-By: GitHub Copilot <noreply@github.com>

---------

Co-authored-by: GitHub Copilot <noreply@github.com>
2026-10-23 13:45:31 +00:00
Colin Faulkingham 74ea81519b Keep Parameters responsive in large feature histories (#2733) (#4549)
* claim: #2733

* Keep Parameters responsive with many feature groups (#2733)
2026-10-23 13:45:26 +00:00
SS-EN 769dfff1a5 Section Analysis: a Hatch option (#4563) (#4565)
* claim: #4563

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

* Section Analysis: a Hatch option, off for a plain cut in the body's colour (#4563)

The Section Analysis dialog gets a Hatch check box beside Flip, and
inspect.section a hatch?: bool (default true), so scripts, the CLI and the
control channel reach it too. Hatched, a cut is its body's colour tinted
toward the cap colour with 45 degree stripes, as before; off, it is the
body's own solid colour. The viewport (cap alpha in the uniform), the CPU
rasterizer and ui.render follow it, and the section keeps it when hidden
and shown again or edited.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:45:23 +00:00
SS-EN 3ef68c708f Expression suggestions stay closed while another control is dragged (#4589) (#4590)
* claim: #4589

* Expression suggestions stay closed while another control is dragged (#4589)

A drag elsewhere leaves a text box its focus (egui gives it up on a click,
not a drag), so dragging the Drive Joints slider rewrote the focused Angle
box under its old selection. A letter before the caret ("5.0 d|eg") made a
new word, a fresh undismissed popup, and the list opened over the slider.

Dismissed suggestions now stay closed until the box is typed in, a drag of
another widget dismisses them, and a hidden list forgets its row rects.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:45:17 +00:00
Reefwalker 3ed1829cc7 fix: retain circular sketch pattern Quantity expressions (#4435) (#4607) 2026-10-23 13:45:15 +00:00
raykholo b982ccd8c4 Mesh Convert works without activating the mesh's component first (#4609) (#4613)
* claim: #4609

* Mesh Convert works without activating the component first (#4609)

Inserting a mesh puts it in a new component while the parent stays
active, so Convert Mesh always refused the first time. Convert now
resolves the mesh body's owning component and adds the conversion
there when the active component is that component or an ancestor,
leaving the active component unchanged. From another branch it still
refuses, and the error names the component to activate.
2026-10-23 13:45:13 +00:00
dudedagger42 2586b09f5a Control: a whole-design measure reads a copy off the window thread (#4604) (#4614)
* Control: a whole-design measure reads a copy off the window thread

`inspect.measure` and `document.inspect` (which measures every body by
default) ran on the window thread. On a large assembly that took about a
minute, the window answered nothing meanwhile, and Windows closed it as
not responding (#4604). They change nothing, so they now run on a copy of
the session on a worker and are answered from there, like exports, but
whatever the "agent commands off the window thread" preference says: an
answer from the design as it was when asked has no order to keep. The
window keeps answering `ui.*` and drawing meanwhile.

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

* Control: clearance and interference checks read a copy off the window thread too

inspect.clearance and inspect.interference only read the design, and on a
large assembly measure every pair of bodies: on a 153-body design a clearance
check computed for over five minutes on the window thread, the window showing
"not responding". They join inspect.measure and document.inspect in running on
a copy, answered from there.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:45:10 +00:00
DuBearte a5f9ff45dd Sketch Symmetry: a dimensioned shape slides onto the axis (#4030) (#4626)
* claim: #4030

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

* Sketch Symmetry: a dimensioned shape slides onto the axis (#4030)

With a rectangle's width dimensioned, Symmetry of its two sides about an
axis was refused: the second side can't move on its own, the axis never
moves, and the ordinary solve that slides the whole rectangle only ran
when every first-picked entity could move. One movable first entity is
now enough.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:45:08 +00:00
raykholo 61360c3b0d Convert to Sheet Metal: a bend picked on its tangent edge connects, on rotated parts too (#4328) (#4630)
* claim: #4328

* Fix sheet conversion picks on tangent bend edges (#4328)

Resolve cylindrical bends through the nearest edge's incident faces when a
pick on a tangent boundary resolves to its planar panel. This keeps valid
remote bends connected, including picks on the other skin of a shell.

Keep conversion geometry and picks in the body's local frame, and format
conversion diagnostics through its world placement. Cover rotated bend
picks and disconnected-pick coordinates with synthetic regressions.
2026-10-23 13:45:06 +00:00
Reefwalker eec6da060f Measure: report obtuse angles at a shared corner (#4625) (#4634)
* claim: #4625

* fix: measure obtuse corners independently of edge direction (#4625)
2026-10-23 13:45:04 +00:00
DuBearte f422bb1263 Intersection edges divide at 1e-6 when asked for less, instead of panicking (#4623) (#4650)
A join failed with the kernel's internal error "tolerance must be no less
than 1e-6" at vendor/truck-geometry/src/decorators/intersection_curve.rs:172:
IntersectionCurve::parameter_division asserted truck's minimum tolerance,
and the guard turned the panic into a failed operation. A tolerance below
truck's TOLERANCE, or one that is not a number, now divides at TOLERANCE:
the curve's points are only found to within it anyway.

The test divides the intersection edges of a hole cut through a B-spline
face at 5e-7, 1e-12, 0, -1 and NaN: each gives the division at 1e-6. Without
the fix it panics with the reported message at the reported line.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:56 +00:00
Martin Pålsson 96ce31153c Pattern and mirror: a rib as the source (#4593) (#4667)
* claim: #4593

* Pattern and mirror: a rib as the source (#4593)

A pattern or mirror of features refused a rib as a source ("Rib1
cannot be patterned or mirrored yet"). A rib's walls run until they
meet the body, so each copy is built again where it is: the rib's
sketch is moved by the copy's transform and the walls are run against
the bodies there, as a hole is drilled again at each copy. The copies
join the body, as the rib does. The rib's own tool building moves into
rib_tools, which the rib feature calls unmoved.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:52 +00:00
Steve Jason a9c3c8bb8a Accept a leading = in expressions, so a fillet radius can be =d3 (#4681) (#4686)
* Sketch fillet radius: accept a leading = in an expression (#4681)

* A second = after a name is still not a definition
2026-10-23 13:44:48 +00:00
dudedagger42 a147a7c083 Sketch Point: a point put on a midpoint or a curve snap is held there (#4691) (#4745)
* Sketch Point: a point put on a midpoint or a curve snap is held there (#4691)

The Point tool sent only a snapped point's coordinates and sketch.point
took only [x, y], so a point clicked on a line's midpoint (or on a curve,
or a model vertex) was free to move off it. sketch.point now takes the
snap forms the other draw commands take ("mid:<line>", "on:<curve>:x,y",
{vertex}, {on_edge}); the snap makes the point with its constraint. The
tool passes those snaps as themselves; a snap to an existing point still
goes as coordinates, where `connect` joins them.

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

* Sketch Point: refuse a snap it can't read instead of answering as if it made a point

sketch_project::snap_points leaves a snap it can't parse ("on:c1:abc",
"on:c1", "on:c1:1,2,3") as it was given, and sketch.point then read
that string back as the point it had made: no error, no point, and an id
a script would go on to dimension. It now takes the snap only when it
names a point of the sketch, and otherwise refuses, listing the snap
forms (found by the test agent on #4745).

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:42 +00:00
dudedagger42 89a11d75c0 Imports through the app keep their matrix, name and options (#4676) (#4746)
The app's background loader (file_load::control) kept only `up` and
`names` of a control-channel open or import, so file.insert_step's
`matrix` and `name` were dropped and the part landed at the origin, and
file.insert_mesh lost all its options. It keeps every parameter but the
path, as given; each command checks them as it does when run directly.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:40 +00:00
dudedagger42 84cad43154 Timeline marker: refuse a negative position, and name the command asked for (#4712) (#4749)
timeline.rollback / timeline.roll_to read a negative position as 0, so
an agent passing -1 for "one step back" rolled the whole timeline back
and every body disappeared, with no error. A negative position is now
refused. The two commands shared one function that refused in
roll_to's name; each now refuses in its own.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:38 +00:00
rlartot 5c280b9a94 fr: Répétition instead of Réseau for patterns (#4751)
Nine messages: the pattern tools, their headings and two sample
descriptions. "Réseau" can be mistaken for a network.

Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-23 13:44:36 +00:00
DuBearte 1a953736ff Combine takes bodies clicked in the Browser (#4452) (#4766)
* claim: #4452

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

* Combine takes bodies clicked in the Browser (#4452)

A body row in the Browser only replaced the selection, so with Combine
open its target and tools stayed empty and overlapping bodies could only
be picked in the view. When the open dialog's active input takes bodies,
a body row now goes there, as plane and axis rows already do.

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

* Combine Browser picks: test both layouts and control-channel clicks (#4452)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:32 +00:00
DuBearte 4106332738 Shortcuts: a sketch tool and a model command can share a key (#4163) (#4781)
The Keyboard Shortcuts page refused a key another command used, even a
sketch tool and a model command that never run in the same mode, so
Shift+S could not open Create Sketch in the model and place a point in a
sketch. Edited by hand, the press ran Create Sketch inside the sketch.

A sketch tool (SKETCH tab) and a model command (the other tabs) may now
share a key. A press runs the enabled command of the mode the view is in
first: the tool in a sketch, the model command outside. Two commands of
one mode still clash, and commands on no tab (undo, views, files) clash
with everything.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:29 +00:00
CamWight 9559ad398a Physical material: one rule for a body's material and its density (#4783) (#4785)
* claim: #4783

* Physical material: one rule for a body's material and its density (#4783)

inspect.measure, component.properties, occurrence.properties and the
Properties window took a body's material name from the raw assignment,
but its density from Document::density, which falls back to the default
material when the assigned one is unknown or has an unusable density. So
a damaged or hand-edited design read one material and weighed another: a
design material "Broken" at -3 g/cm³ read "Broken" at 1.29 g/cm³.

One rule, in one place:
- DEFAULT_MATERIAL (new, beside DEFAULT_DENSITY in the material library)
  is the default material's name. The library's first row uses it.
- Document::body_material (new, beside Document::density) is the material
  a body's mass is computed with, as the library or the design spells it:
  the assigned one when it is known and has a usable density, else
  DEFAULT_MATERIAL. Document::density is that material's density, with
  the same value as before for every input.

inspect.measure and the physical properties name body_material.
inspect.measure now names every body's material, measured or not, since
naming it takes no measuring; the density stays with the mass, on the
bodies measured. So the Properties window shows the engine's name without
a fallback of its own. The places that keep the assignment as stored
(configuration cells, material.assign, material.edit, material.delete
and the dialogs) only use the constant, so a configuration still keeps a
damaged assignment as it is.

The visible change: a body whose material is unknown, or has an unusable
density, reads "Default", the material it weighs, and a miscased name
reads as the library or the design spells it. A body inspect.measure
can't measure now has its material too. Masses and densities don't
change.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:27 +00:00
DuBearte bddb52c96a Move: the value box follows the dragged axis, and Tab steps through X, Y, Z (#4776) (#4788)
The Move dialog's floating value box always showed and edited the X
distance, whichever arrow was dragged, and Tab left the box for another
widget. Now:
- dragging an arrow shows that axis's distance, turning a ring (Free Move)
  that axis's angle; a plane square or the ball leaves the box as it is;
- Tab in the box goes to the next value (X, Y, Z, then in Free Move the
  X, Y and Z angles) and Shift+Tab back, the keyboard staying in the box.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:25 +00:00
raykholo a0fd39a7be Flange on a plain solid says how to start sheet metal (#2987) (#4789)
* claim: #2987

* Flange on a plain solid says how to start sheet metal (#2987)

Flange, Flat Pattern, and Export DXF on a design with no sheet metal body
now say how to make one: Flange > Base, or Convert to Sheet Metal on a
plate's large flat face. An edge flange checks for a sheet metal body
before it adds a feature. The message is in the catalogs, and engine
messages that match a catalog entry are shown in the interface language.

* Dialogs show a refusal without the engine's parameter prefix (#2987)

The Flange dialog showed "invalid parameters for `sheet.flange`: " in front
of the new guidance. The dialog already names its command, so the red
refusal text leaves that prefix out and follows the interface language
when the message is in the catalogs. Create Issue still gets the full text.
2026-10-23 13:44:24 +00:00
raykholo be87303683 Web: a trackpad swipe pans instead of zooming (#4116) (#4834)
* claim: #4116

* Web: a trackpad swipe pans instead of zooming (#4116)

Browsers deliver trackpad swipes as wheel events without scroll phases, so
the shared navigation treated every vertical swipe as a mouse-wheel zoom.
The web raw-input hook now brackets point-mode swipes (small or horizontal
deltas) with gesture start/end phases, ending after 100 ms idle or before
line/page wheel or pinch input. Wheel notches and pinch still zoom, and
camera input still goes through navigation::handle.
2026-10-23 13:44:20 +00:00
raykholo bd0cc4bb51 Units of new designs apply to the empty design already open (#221) (#4835)
* claim: #221

* Units of new designs apply to the blank design already open (#221)

Changing Preferences > Units of new designs only took effect at the next
File > New, so the empty design open at launch stayed in millimetres. When
the preference changes (the dialog, Cancel, Restore Defaults, or
preferences.set) and the open design is blank and unsaved, it takes the new
units at once, without an undo step or unsaved changes. A design with
content or a file keeps its units.
2026-10-23 13:44:18 +00:00
raykholo 4c8ae5d078 Hem: the direction arrow points the way the hem builds (#4794) (#4836)
* claim: #4794

* Hem: the direction arrow points the way the hem builds (#4794)

The Hem dialog shared the edge flange's arrow, which points out of the
sheet's larger neighbouring face. A hem folds back the other way, so the
arrow pointed away from the material it adds. Flip the hem's arrow so it
matches the geometry; edge flanges keep theirs.

A regression test compares the arrow with the side the finished hem adds
material on, for both Flip states, top and bottom edge picks, and both
dialog layouts.
2026-10-23 13:44:17 +00:00
Mateusz Sych e33c371e99 Model cache: thin the checkpoints of a design too big for its file instead of leaving the cache out (#3220) (#4882)
* claim: #3220

* Model cache: keep fewer states of a design too big for its file instead of none (#3220)

A model cache over the container's budget keeps the final state, then the
slowest features' states while the bodies they add fit (a body several
states hold counts once), and is left out only when not even the final
state fits. Opening evaluates just the features whose state the file left
out, each from the state before it, so the model is whole afterwards.

* Model cache: keep each encoded body apart, copy only what a cache stores

Encoding a model too big for its file no longer copies every distinct body
into one buffer before thinning: the payload is copied from the encodings
once its size fits, so saving holds no more than before.

* Model cache tests: canonical runs in the timeline fixture; rustfmt

* Model cache: count the records a thinned cache keeps; always try the final state

Thinning counted only the bodies, so a model of many copies of one part
(records, not bodies, filling the room) shrank its budget to nothing and
saved no cache although its final state fitted. The choice now counts the
records of what it keeps, always keeps the final state, and says what it
counted, so each retry keeps less and the final state alone is always tried.

* docs: a thinned model cache counts its records and always tries the final state

* Model cache test: the slowest feature's state holds bodies 16 to 23

* Model cache: thinned opens with fingerprints that read the state before a feature

Since #3397 a feature's fingerprint reads where the bodies before it lie.
After a state the file left out, adopt takes the saved fingerprint and the
evaluation that works the state out checks it. A result evaluated ahead
is never taken as one the file left out. The thinned round trips include
the placed components' design, whose cut reads those placements.

---------

Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com>
2026-10-23 13:44:11 +00:00
raykholo e104e241a3 Rename a body: keep the names of siblings a later feature removed (#4880) (#4904)
* claim: #4880

* Rename a body: keep the names of siblings a later feature removed (#4880)

Renaming a body sets the stored names of all bodies its feature made, so
the siblings are not renumbered. Those names were read from the final
model, where a body that a later feature removed no longer exists: its
stored name was lost, it came back as `Ext (3)`, and the later Remove
failed with "no body `Body3`". Read the siblings from the feature's own
result instead, as the later features already are.

* Test: renaming one half of a split leaves the other

Renaming one half keeps the other half's name, also once a later feature
joins that other half away, after both an incremental and a full
evaluation.
2026-10-23 13:44:09 +00:00
raykholo 3bca787213 MCP tools: unknown command ids suggest the closest ids (#4846) (#4915)
* claim: #4846

* MCP tools: unknown command ids suggest the closest ids (#4846)

An unknown command id in `execute` or `batch` now lists up to three closest
ids, and an id with parameters packed into it says to pass them in `params`.
2026-10-23 13:44:07 +00:00
raykholo 21257c39c2 Keyboard Shortcuts page: draw only the visible rows (#4804) (#4919)
* claim: #4804

* Keyboard Shortcuts page: draw only the visible rows (#4804)

Off-screen rows keep their height but skip their cells, the list is
filtered once before measuring and drawing, and each row's shortcut
comes from the command spec already in hand instead of a registry
lookup.
2026-10-23 13:44:06 +00:00
raykholo ed6e123d54 Sketch: offer Break Offset Association for offsets of lines (#4899) (#4920)
* claim: #4899

* Sketch: offer Break Offset Association for offsets of lines (#4899)

Trim and Extend refuse an associated offset output and point to Break
Offset Association, but the right-click menu offered it only for
free-form offsets. Offer it whenever the curve is an offset output
(analytic, topology or free-form), using the sketch's shared lookup.
2026-10-23 13:44:04 +00:00
raykholo dad28abecf solidcraft-cli bench-assistant: the standard Assistant model test (#4851) (#4930)
* claim: #4851

* solidcraft-cli bench-assistant: the standard Assistant model test

Runs the eleven v1 tasks of #4851 against a desktop app that is already
running with a control port. Each task starts a new design and a new chat
with All permissions, runs only the prescribed setup, sends the prompt
once and waits for the turn. The result is checked on the design
(document.inspect with measurements): body count, volume within 1%
(0.3% for the fillet), bounding-box size in any orientation, the lowest
corner, the body name, live feature kinds, and changed or unchanged
content; the volume question is checked on the reply. Failures get the
issue's categories from the conversation's items. Prints a table, and
writes the issue's comment template (--markdown) and the raw results
with each conversation (--json).
2026-10-23 13:44:02 +00:00
Jasper van Loenen d1f5d6e223 History: drag features across the marker in one step, clearer drop feedback (#4938)
Dropping a feature just behind the history marker now rolls it back, and
dropping it just before the marker makes it active again, in both the
bottom strip and the left panel. timeline.reorder and timeline.move_many
take an optional `active` param that sets the marker in the same command,
so the move is one undo step and one recompute. A position on the wrong
side of the marker for the requested `active` is refused.

Drop feedback:
- bottom strip: the drop line sits centred between two features (it
  used to touch the previous one) and shows at the first position
  (it used to be drawn outside the strip there)
- left panel: a drop slot below the marker when it is at the end
- no drop line when the marker is dragged back to where it is
- a dragged feature is outlined in the drop line's green
- grab cursor on the marker, grabbing cursor while dragging the marker
  or a feature

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:44:00 +00:00
Logan Night 1b215b521d icon_audit: compare the requested-icons manifest with CRLF checkouts ignored (#4932) (#4960)
* claim: #4932

* icon_audit: compare the requested-icons manifest with CRLF checkouts ignored (#4932)

On Windows, Git for Windows checks out requested_icons.txt with CRLF line
endings (core.autocrlf; the repo has no .gitattributes), while the test
builds the manifest with \n, so the comparison fails although the lists
are the same. Read the file with \r\n normalised to \n, as #1931 did for
the icon tools. Regenerating still writes \n.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 13:43:56 +00:00
raykholo 36a354e3a7 Agent chat: serve the bundled skills' reference through an MCP tool (#4966) (#4970)
* claim: #4966

* Agent chat: serve the bundled skills' reference through an MCP tool (#4966)

The bundled skills start with "read reference.md", but SolidCraft's own
chat turns the agent's file tools off, so its agent could not follow them.

The MCP server gains a read-only `skill_reference` tool that returns a
skill's documents from copies built into the binary. It takes a skill
name and a file name from a fixed list, never a path, and refuses an
unknown skill or file with the available ones. The skills point to it,
and docs/mcp.md and skills/README.md describe it.
2026-10-23 13:43:54 +00:00
1173 changed files with 337936 additions and 5322 deletions
+57 -1
View File
@@ -42,7 +42,6 @@ filter = "binary(action_matrix) & test(=action_matrix_sample)"
threads-required = 12
priority = 100
# The slowest tests start first, so a shard does not end with one of them running alone on a
# core while the others sit idle (seed_81 alone takes about 20 minutes under CI load). Medians
# from the CI test timings (the junit artifacts), slowest first; a test over ~100 s belongs here.
@@ -69,3 +68,60 @@ filter = """
))
"""
priority = 100
# Each nextest test has its own process/device. Concurrent cold lavapipe path
# compiles contend for CPU and can exhaust the 600 s pipeline setup budget.
# Serialize this module per CI shard; other tests retain their normal parallelism.
[test-groups.gpu-path]
max-threads = 1
[[profile.ci.overrides]]
filter = "package(=solidcraft-gpu) & test(/^path::/)"
test-group = "gpu-path"
# Full local validation opts into its own scheduling policy. Keep GitHub CI's
# worker reservation and slow-test priorities above independent of this runner.
[profile.local-ci]
inherits = "ci"
[[profile.local-ci.overrides]]
filter = "binary(action_matrix) & test(=action_matrix_sample)"
threads-required = "num-cpus"
priority = 100
[[profile.local-ci.overrides]]
filter = "(package(solidcraft-engine) & test(=tests::robustness_goldens::seed_81)) | (package(solidcraft-kernel) & test(=tspline::tests::hostile_tsplines_never_panic))"
priority = 99
[[profile.local-ci.overrides]]
filter = """
(package(solidcraft-engine) & (
test(/^tests::robustness_goldens::seed_(81|45|20|24|95|02|57|40|18|48|32|46|85|61)$/)
| test(/^tests::robustness::/)
| test(=tests::hostile_params_never_panic)
| test(=cmd::clearance::tests::distances_are_exact_on_edges_and_curved_faces)
| test(=cmd::clipboard::tests::a_pasted_coil_joins_the_pasted_body)
| test(=tests::pleat_join::the_pleats_join_at_every_wall_from_2_4_to_2_6_mm)
| test(=tests::flush_join_loft::a_flange_flush_with_the_end_of_a_bent_tapered_tube_joins)
| test(=cmd::sheet::tests::perforated_sheet_takes_a_thousand_round_holes)
| test(=boolean_regression_tests::joined_loft_frames_plan_their_display_at_every_quality)
))
| (package(solidcraft-engine) & (binary(atomic_commands) | binary(step_export_regressions)))
| (package(solidcraft-kernel) & (
test(=curveblend::tests::rolling_ball_turns_about_a_rounded_corner_of_its_radius)
| test(=tests::fillet_tabbed_plate_outer_loop_and_partial_selection_after_hole)
| test(=tspline::tests::hostile_tsplines_never_panic)
| test(=fill::tests::regular_rims_of_three_to_eight_sides_fill_with_one_face_and_close_the_cup)
| test(/^issue_788_tests::equal_bores_crossing_/)
))
"""
priority = 98
# Native Windows shader/device creation must not overlap within the local run.
[test-groups.windows-gpu]
max-threads = 1
[[profile.local-ci.overrides]]
platform = "cfg(windows)"
filter = "package(solidcraft-gpu) | package(solidcraft) | (package(solidcraft-ui-egui) & test(/(offscreen|gpu)/))"
test-group = "windows-gpu"
+3
View File
@@ -0,0 +1,3 @@
# Preserve canonical source receipts and the runtime icon-discovery snapshot.
.gitattributes text eol=lf
/assets/icons/ui/design/requested_icons.txt text eol=lf
+480 -2
View File
@@ -14,9 +14,33 @@ Editable drawing code, native projects and shipped exports are collected under `
| File | Author | Source | Licence |
|---|---|---|---|
| assets/render/catalog/capabilities.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/capabilities.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
| assets/render/catalog/environments.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/environments.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
| assets/render/catalog/lighting.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/lighting.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
| assets/render/catalog/materials.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/materials.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
| assets/render/catalog/quality-presets.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/quality-presets.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
| assets/render/catalog/scene-presets.json | SolidCraft contributors | Byte-identical runtime export of docs/render-materials/scene-presets.json; generated and checked by tools/render-library/generate.py | MIT OR Apache-2.0 |
| examples/chebyshev-motion-trace.json | SolidCraft contributors | Original parametric Chebyshev lambda mechanism and associative-trace demo, constructed from analytic circle intersections; classical link proportions documented at https://doi.org/10.1002/rcs.70095; no external CAD geometry or artwork | MIT OR Apache-2.0 |
| examples/material-card.json | SolidCraft contributors | original analytic box and explicitly synthetic engineering card; invented illustrative constants, no external material dataset | MIT OR Apache-2.0 |
| examples/studio/author_sweep.py | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
| examples/studio/author_scene.py | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
| examples/studio/materials.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-exploded-sweep.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-exploded-render.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-exploded.json | SolidCraft contributors | original editable CAD scene and authoring metadata; no external assets | MIT OR Apache-2.0 |
| examples/studio/stage1-sweep.json | SolidCraft contributors | original tangent circular-arc CAD cyclorama; no external assets | MIT OR Apache-2.0 |
| examples/studio/stage1.json | SolidCraft contributors | original editable CAD studio scene; no imported geometry or external image assets | MIT OR Apache-2.0 |
| examples/studio/stage1-render.json | SolidCraft contributors | original studio camera and lighting parameters | MIT OR Apache-2.0 |
| examples/render-studio.json | SolidCraft contributors | original procedural box/cylinder enclosure script for the integrated Render workspace | MIT OR Apache-2.0 |
| examples/render-opaque-studio.json | SolidCraft contributors | original procedural sphere study varying opaque metal fraction and roughness independently; no external assets or engineering data | MIT OR Apache-2.0 |
| crates/gpu/src/path/denoise.wgsl | SolidCraft contributors | original bounded edge-aware a-trous spatial filter; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
| crates/gpu/src/path/denoise_guides.wgsl | SolidCraft contributors | original deterministic centre-ray material and geometry guide pass; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
| crates/gpu/src/path/shadows.wgsl | SolidCraft contributors | original bounded area, sun and environment visibility transport; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
| crates/gpu/src/path/textures.wgsl | SolidCraft contributors | original planar colour and normal map sampling; GPU2 donor d710e31, no external shader source | MIT OR Apache-2.0 |
| assets/appearances/starter_pack.rs | SolidCraft contributors | original illustrative optical recipes, authored scalar values and future procedural resource descriptors; no measured or external material dataset | MIT OR Apache-2.0 |
| examples/material-finishes.json | SolidCraft contributors | original procedural sphere study of six illustrative opaque finishes with generic material densities; no external assets or measured optical data | MIT OR Apache-2.0 |
| examples/render-path-studio.json | SolidCraft contributors | original procedural opaque enclosure, teal insert and metal fasteners for progressive rendering; no imported models | MIT OR Apache-2.0 |
| crates/gpu/src/path/path.wgsl | SolidCraft contributors | original software BVH traversal and path integration using the existing SolidCraft surface/studio equations; no external shader source | MIT OR Apache-2.0 |
| crates/gpu/src/pbr.rs | SolidCraft contributors | original WGSL translation of SolidCraft's opaque surface and fixed studio reference, embedded as Rust source; no external shader code | MIT OR Apache-2.0 |
| assets/icons/app/macos/source/SolidCraft.icon/icon.json | SolidCraft contributors | approved Icon Composer composition and appearance settings for the original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/app/macos/source/SolidCraft.icon/Assets/02-grid.svg | SolidCraft contributors | original perspective grid with elliptical radial alpha, exported from VectorCraft | MIT OR Apache-2.0 |
@@ -25,6 +49,8 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/icons/app/macos/exports/Assets.car | SolidCraft contributors | Apple actool compilation of assets/icons/app/macos/source/SolidCraft.icon for macOS; contains original artwork only | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/source/icon.vectorcraft | SolidCraft contributors | retained editable historical VectorCraft source, with named face and background layers; redundant exports retired | MIT OR Apache-2.0 |
| assets/icons/app/macos/exports/icon.icns | SolidCraft contributors | macOS fallback compiled by Apple's actool from the approved assets/icons/app/macos/source/SolidCraft.icon composition | MIT OR Apache-2.0 |
| assets/models/test_boat.bin | 3DBenchy.com | 3DBenchy.com | Public Domain |
| assets/models/make_test_boat.py | SolidCraft contributors | original script that packs a model into the compact file above | MIT OR Apache-2.0 |
| docs/screenshots/class-a-blend.png | SolidCraft contributors | screenshot of SolidCraft itself: two boxes bridged by `surface.blend` (a 40x10 sheet, `solidcraft-cli snapshot` of plan/evidence script ev_blend) | MIT OR Apache-2.0 |
| docs/screenshots/class-a-crown.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch crowned with `surface.patch crown=0.3` over a saddle rim (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
| docs/screenshots/class-a-boundary.png | SolidCraft contributors | screenshot of SolidCraft itself: a patch through two sketch lines built by `surface.boundary` with `crown: 0.4` (`solidcraft-cli snapshot`) | MIT OR Apache-2.0 |
@@ -96,6 +122,7 @@ Editable drawing code, native projects and shipped exports are collected under `
| tests/oracle/63-hinge-knuckle/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/63-hinge-knuckle/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
| tests/oracle/65-sketch-gear-tooth/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/65-sketch-gear-tooth/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
| assets/appearances/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/appearances/opaque_finishes.rs | SolidCraft contributors | original illustrative opaque finish values; no external dataset, textures or measured engineering data | MIT OR Apache-2.0 |
| assets/fonts/biz-udp-gothic/OFL.txt | The BIZ UDGothic Project Authors | Unmodified accompanying font licence | 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 |
@@ -343,6 +370,7 @@ Editable drawing code, native projects and shipped exports are collected under `
| assets/icons/ui/design/svg/flat/stop.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/flat/shield.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/status_recovery.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/frame_member.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/placeholder.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/plane.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/icons/ui/design/svg/model/plane_angle.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
@@ -526,9 +554,9 @@ crates/engine/src/tests/gltf_import.rs); no third-party models are committed.
## FreeCAD and Rhino import library
FreeCAD `.FCStd` and Rhino `.3dm` files are read with [cadmpeg](https://github.com/cadmpeg/cadmpeg)
0.6.1 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
0.6.3 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
`cadmpeg-container`, `cadmpeg-codec-freecad`, `cadmpeg-codec-rhino` and `cadmpeg-codec-step`
in native builds (`crates/io/src/native_cad.rs`). Only those open-format codecs are built: no
in native and browser builds (`crates/io/src/native_cad.rs`). Only those open-format codecs are built: no
default features, so cadmpeg's codecs for commercial CAD formats and its ShapeManager/ACIS
codec are not compiled in. Its licence text is in THIRD-PARTY-LICENSES.txt; no code from it is
copied into SolidCraft.
@@ -561,3 +589,453 @@ A frame whose meshes the GPU refuses to stage (device lost or out of memory) is
skipped with a warning instead of panicking (#1469).
Paint callbacks are prepared only after a drawable is acquired, so a skipped
surface frame cannot strand cached meshes without their GPU initialization.
## Runtime studio lighting mathematics
`crates/gpu/src/path/path.wgsl` implements the power-heuristic estimator from the
mathematical description in [PBRT fourth edition, section 13.4](https://pbr-book.org/4ed/Light_Transport_I_Surface_Reflection/A_Better_Path_Tracer).
No PBRT code or assets were copied. `crates/render/src/job/lighting.rs` evaluates
[Krzysztof Narkowicz's published 2016 ACES rational fit](https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/)
using its published coefficients (author licenses the fit under CC0/MIT).
These original implementations are under SolidCraft's MIT OR Apache-2.0 licence.
## Original coffee framing variant
| File | Author | Provenance | Licence |
|---|---|---|---|
| examples/studio/author_coffee_framing.py | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-framing.json | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-framing-sweep.json | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-framing-render.json | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-framing.md | SolidCraft contributors | original editable composition, camera and documentation; no external assets | MIT OR Apache-2.0 |
## Original cylindrical coffee housing variant
| File | Author | Provenance | Licence |
|---|---|---|---|
| examples/studio/author_coffee_cylinder.py | SolidCraft contributors | original parametric cylindrical CAD construction and editable command recipes; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-cylinder.json | SolidCraft contributors | original cylindrical housing on the original exploded coffee composition; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-cylinder-sweep.json | SolidCraft contributors | original cylindrical housing and continuous CAD cyclorama; no external assets | MIT OR Apache-2.0 |
| examples/studio/coffee-cylinder-render.json | SolidCraft contributors | original camera, studio lighting and optical settings retained from the framing variant | MIT OR Apache-2.0 |
| examples/studio/coffee-cylinder.md | SolidCraft contributors | original dimensional recipe, provenance and validation limitations | MIT OR Apache-2.0 |
## Runtime environment sampling
`crates/render/src/lighting/environment.rs` and the environment/sun portions of
`crates/gpu/src/path/path.wgsl` are original SolidCraft implementations of
solid-angle importance sampling and MIS, informed by the mathematical description
in [PBRT fourth edition, sections 12.5](https://pbr-book.org/4ed/Light_Sources/Infinite_Area_Lights)
and [13.4](https://pbr-book.org/4ed/Light_Transport_I_Surface_Reflection/A_Better_Path_Tracer).
The procedural gradient and inline RGBE test fixtures are original mathematical
colours, not downloaded imagery. No third-party environment images or shader code
are included. Licence: MIT OR Apache-2.0.
## Original tumbler showcase assets
The CAD room, tumblers, window opening, wood/weave patterns and RGBE sky below
are original SolidCraft contributor work. No third-party scene, photograph, HDRI,
texture, or AI-generated artwork was used. Python authoring sources are included.
All listed files use the repository MIT OR Apache-2.0 licence.
| File | Source / author | Use | License |
| --- | --- | --- | --- |
| examples/studio/author_showcase.py | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/author_tumblers.py | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/textures/ATTRIBUTION.md | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/textures/author_textures.py | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/textures/environment-manifest.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/textures/manifest.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-hdr-textures-blocked-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-hdr-textures-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-materials.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-sun-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-sun-textures-blocked-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-sun-textures-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-sun.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-textures-blocked-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers-textures-render.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
| examples/studio/tumblers.json | Original SolidCraft contributors | Editable scene / runtime resource / reproducible authoring metadata | MIT OR Apache-2.0 |
The original studio generators produce local ignored PNG maps and an RGBE gradient.
Generated image bytes are not source assets; their authorship, licences and expected
hashes are documented under `examples/studio/textures`. Rendered evidence belongs
on the results branch. No photographs, scans or external image assets are included.
## Additional render and showcase resources
| File | Author | Source | Licence |
|---|---|---|---|
| assets/render/starter/tools/check_all.py | SolidCraft contributors | original read-only authored/native resource validation orchestration and evidence receipts | MIT OR Apache-2.0 |
| assets/render/starter/tools/test_check_all.py | SolidCraft contributors | original validation-runner regression fixtures and subprocess tests | MIT OR Apache-2.0 |
| assets/render/starter/tools/requirements.txt | SolidCraft contributors | pinned offline authoring tool requirements; no application dependency changes | MIT OR Apache-2.0 |
| crates/render/examples/validate_starter.rs | SolidCraft contributors | original native resource conformance checker using public SolidCraft decoding, normalization, sampling and mip APIs | MIT OR Apache-2.0 |
| .gitattributes | SolidCraft contributors | original canonical line-ending policy for the existing runtime icon-discovery manifest; no artwork or icon-assignment changes | MIT OR Apache-2.0 |
| assets/render/.gitattributes | SolidCraft contributors | original canonical line-ending policy for SHA-256-pinned renderer authoring resources | MIT OR Apache-2.0 |
| docs/render-materials/.gitattributes | SolidCraft contributors | original canonical line-ending policy for SHA-256-pinned renderer authoring resources | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/assembly-bench-soft.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/automotive-turntable.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/black-metal-strips.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/cnc-overhead.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/esd-bench-neutral.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/fdm-neutral-inspection.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/garage-door.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/hangar-skylight.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/home-workbench.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/makerspace-worklights.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/mechanical-bay.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/neutral-comparison.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/neutral-side-softbox.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/paint-inspection-bands.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/printed-part-photo.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/rework-offset-strips.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/robot-led-editorial.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/showroom-ribbons.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/spacecraft-clean-bay.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/sun-free-overcast.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/vacuum-black.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/warm-table-window.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/white-enveloping-studio.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/exports/woodshop-window.hdr | SolidCraft contributors | original analytic relative-radiance HDR export; recipe, linear-sRGB primaries and SHA-256 in assets/render/starter/environments/manifest.json; not measured photometry | MIT OR Apache-2.0 |
| assets/render/starter/environments/manifest.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/environments/source/recipes.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/lighting/exports/rigs.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/lighting/recipe.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-001/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-001/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-001/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-001/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-001/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-002/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-002/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-002/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-002/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-002/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-003/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-003/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-003/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-003/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-003/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-004/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-004/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-004/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-004/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-004/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-005/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-005/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-005/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-005/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-005/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-006/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-006/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-006/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-006/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-006/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-007/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-007/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-007/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-007/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-007/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-008/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-008/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-008/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-008/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-008/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-009/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-009/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-009/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-009/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-009/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-010/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-010/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-010/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-010/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-010/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-011/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-011/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-011/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-011/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-011/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-012/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-012/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-012/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-012/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-012/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-013/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-013/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-013/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-013/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-013/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-014/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-014/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-014/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-014/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-014/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-015/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-015/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-015/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-015/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-015/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-016/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-016/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-016/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-016/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-016/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-017/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-017/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-017/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-017/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-017/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-018/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-018/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-018/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-018/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-018/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-019/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-019/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-019/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-019/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-019/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-020/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-020/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-020/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-020/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-020/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-021/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-021/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-021/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-021/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-021/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-022/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-022/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-022/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-022/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-022/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-023/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-023/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-023/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-023/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-023/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-024/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-024/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-024/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-024/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-024/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-025/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-025/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-025/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-025/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-025/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-026/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-026/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-026/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-026/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-026/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-027/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-027/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-027/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-027/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-027/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-028/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-028/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-028/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-028/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-028/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-029/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-029/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-029/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-029/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-029/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-030/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-030/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-030/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-030/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-030/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-031/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-031/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-031/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-031/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-031/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-032/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-032/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-032/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-032/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-032/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-033/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-033/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-033/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-033/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-033/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-034/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-034/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-034/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-034/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-034/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-035/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-035/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-035/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-035/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-035/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-036/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-036/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-036/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-036/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-036/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-037/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-037/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-037/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-037/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-037/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-038/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-038/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-038/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-038/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-038/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-039/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-039/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-039/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-039/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-039/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-040/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-040/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-040/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-040/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-040/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-041/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-041/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-041/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-041/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-041/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-042/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-042/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-042/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-042/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-042/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-043/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-043/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-043/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-043/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-043/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-044/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-044/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-044/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-044/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-044/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-045/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-045/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-045/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-045/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-045/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-046/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-046/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-046/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-046/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-046/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-047/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-047/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-047/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-047/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-047/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-048/base_color.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-048/height.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-048/metallic.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-048/normal_open_gl.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/exports/SURF-048/roughness.png | SolidCraft contributors | original deterministic periodic PBR map export; recipe, role, scale and SHA-256 in assets/render/starter/materials/manifest.json; not measured material data | MIT OR Apache-2.0 |
| assets/render/starter/materials/manifest.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/materials/source/recipes.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/pack.json | SolidCraft contributors | original clean-room renderer resource recipe or manifest; authored/unbound scope and source receipts in assets/render/starter/README.md | MIT OR Apache-2.0 |
| assets/render/starter/tools/environments.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/starter/tools/lighting.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/starter/tools/materials.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/starter/tools/test_environments.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/starter/tools/test_lighting.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/starter/tools/test_materials.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/starter/tools/validate_pack.py | SolidCraft contributors | original clean-room offline resource authoring, numerical verification or validation tool; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/selene/licenses/UFL.txt | Canonical Ltd. | exact Ubuntu Font Licence 1.0 notice retained from https://github.com/0xlillith/solidcraft-rover/blob/b32437915372d32b71419dc8f5143fe22ff1e869/UFL.txt for the modelled Ubuntu Light lettering | Ubuntu Font Licence 1.0 |
| assets/render/selene/materials/appearance-pack.json | SolidCraft contributors | original clean-room English appearance authoring data: 48 illustrative opaque PBR projections with separate advanced target specifications; no measured dataset or external assets | MIT OR Apache-2.0 |
| assets/render/selene/materials/appearance-swatches.json | SolidCraft contributors | original analytic command script for 48 opaque box swatches and a support base, derived only from the adjacent original appearance pack; no external artwork or engineering properties | MIT OR Apache-2.0 |
| assets/render/selene/models/SELENE-functional.solidcraft | SolidCraft contributors (native enhancements); 0xlillith (original rover); SychPL / Mateusz Sych (supplied showcase assembly); Canonical Ltd. (lettering outlines) | native-command derivative of the unchanged author-supplied source, with original bearing, suspension, gripper, electronics and presentation work; source and final hashes in assets/render/selene/SOURCE.md and VALIDATION.md | Model and showcase contributions: MIT OR Apache-2.0 (explicit SychPL grant https://github.com/tangency-labs/solidcraft/pull/5494#issuecomment-6375788360); lettering: Ubuntu Font Licence 1.0, notice retained in assets/render/selene/licenses/UFL.txt |
| assets/render/selene/motion/interference-pairs.json | SolidCraft contributors | original authored exact-body pair selection for bounded native clearance checks on the functional SELENE derivative; not a generated collision result | MIT OR Apache-2.0 |
| assets/render/selene/motion/mechanism-acceptance.json | SolidCraft contributors | original kinematic acceptance criteria, inspected source-container bindings and analytic planning checks from cited public SolidCraft documentation and the admitted native source; derivative runtime acceptance is recorded separately | MIT OR Apache-2.0 |
| assets/render/selene/motion/preflight-commands.json | SolidCraft contributors | original read-only inventory script using existing project commands; no source-model selectors or state mutations | MIT OR Apache-2.0 |
| assets/render/selene/motion/sample-sequence.json | SolidCraft contributors | original future lunar sampling and sealed-vial dynamics state specification; no simulation or physical calibration result | MIT OR Apache-2.0 |
| assets/render/selene/scenes/roles.json | SolidCraft contributors | original semantic role and candidate-material authoring specification for the prepared SELENE demo; no native geometry or external assets bundled | MIT OR Apache-2.0 |
| assets/render/selene/scenes/shots.json | SolidCraft contributors | original shot, framing and staged-sequence authoring specification for the prepared SELENE demo; no render or simulation result claimed | MIT OR Apache-2.0 |
| assets/render/selene/scenes/stages.json | SolidCraft contributors | original stage composition and light-ownership authoring specification derived from the project render catalog; scientific context linked in scenes/README.md | MIT OR Apache-2.0 |
| assets/render/selene/source-lock.json | SolidCraft contributors | original source-admission metadata and public-reference inventory pinning the supplied improved SELENE native file; original-model and font notices preserved in SOURCE.md | MIT OR Apache-2.0 |
| assets/render/selene/source/SELENE-showcase.solidcraft | 0xlillith (original rover); SychPL / Mateusz Sych (showcase assembly and helpers); SolidCraft contributors (emblem); Canonical Ltd. (lettering outlines) | unchanged author-published native file from https://github.com/SychPL/solidcraft/blob/32ec1720447e3fcc6bb6cc53a63fb5b39cfa21b9/SELENE-showcase.solidcraft; source hash and license evidence in assets/render/selene/SOURCE.md | Model and showcase contributions: MIT OR Apache-2.0 (explicit SychPL grant https://github.com/tangency-labs/solidcraft/pull/5494#issuecomment-6375788360); lettering: Ubuntu Font Licence 1.0, notice retained in assets/render/selene/licenses/UFL.txt |
| assets/render/selene/tools/bearings.py | SolidCraft contributors | original native-command authoring of grooved bearings, rolling-element kinematics, clamp hubs and arm/head clearances; analytic geometry, no copied CAD source | MIT OR Apache-2.0 |
| assets/render/selene/tools/build.py | SolidCraft contributors | original Python development tool for pinned-source native command replay, staged checkpoints, command-trace and geometry health checks; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/selene/tools/electronics.py | SolidCraft contributors | original native-command Raspberry Pi 5 visual reconstruction using public manufacturer mechanical dimensions; original package envelopes and connector internals, no manufacturer CAD or artwork copied | MIT OR Apache-2.0 |
| assets/render/selene/tools/export_views.py | SolidCraft contributors | original native MCP preview export tool for three reusable showcase views; changes only transient visibility and cameras and verifies the source master hash | MIT OR Apache-2.0 |
| assets/render/selene/tools/finishes.py | SolidCraft contributors | original native physical-material proxy and opaque PBR assignment generator preserving the supplied rover palette | MIT OR Apache-2.0 |
| assets/render/selene/tools/mechanisms.py | SolidCraft contributors | original native-command gripper sleeves, pads, wheel ownership, joint limits and inspection studies with independent analytic suspension relation | MIT OR Apache-2.0 |
| assets/render/selene/tools/prepare.py | SolidCraft contributors | original Python development tool for bounded native-container inspection and reviewed opaque body-assignment preparation; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/selene/tools/presentation.py | SolidCraft contributors | original native-command browser organization, camera/render setups and closed sample-vial glass preparation | MIT OR Apache-2.0 |
| assets/render/selene/tools/suspension.py | SolidCraft contributors | original native-command trailing-arm suspension, telescoping coilover loops, plain bushings, carrier gussets and local motion clearances derived from the admitted rover geometry; independent analytic pin-distance relation, no copied external CAD | MIT OR Apache-2.0 |
| assets/render/selene/tools/validate_native.py | SolidCraft contributors | original bounded native MCP verification tool for sampled SELENE kinematics, independent analytic follower and world-coordinate checks, optional exact-name interference pairs and unchanged input hashes; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/selene/tools/validate_prepared.py | SolidCraft contributors | original Python development validator for prepared SELENE resources, source status, references, hashes and command projections; no application runtime dependency | MIT OR Apache-2.0 |
| assets/render/selene/tools/yaw_supports.py | SolidCraft contributors | original native-command plain yaw journals, flanged brass thrust bushings, recessed axial retaining pins and mounting-head clearances derived from the admitted rover geometry; no copied external CAD | MIT OR Apache-2.0 |
| assets/render/selene/ui/demo-flow.json | SolidCraft contributors | original progressive-disclosure UI integration specification for the existing Render workspace; no copied UI artwork or runtime registration | MIT OR Apache-2.0 |
| docs/render-materials/baseline-setup-commands.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/capabilities.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/environment-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/environments.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/lighting.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/lunar-physics-fixtures.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/materials.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/provenance.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/quality-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/roadmap.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/scene-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/selene-bindings.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/simulation-requirements.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/studio-presets.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/ui-integration.json | SolidCraft contributors | original English UI integration specification and exact source-revision mappings from SolidCraft's own implemented shell, UI kit and accepted icons; no copied artwork or runtime registration | MIT OR Apache-2.0 |
| docs/render-materials/ux.json | SolidCraft contributors | original clean-room English catalog, preset or analytic planning data; external references documented in docs/render-materials/SOURCES.md; no external assets bundled | MIT OR Apache-2.0 |
| docs/render-materials/validate_catalog.py | SolidCraft contributors | original Python development validator for catalog references, preset composition and analytic specification fixtures; no application runtime dependency | MIT OR Apache-2.0 |
### Built-in render resource adapters (personal integration batch, 2026-10-22)
The original adapters in `crates/engine/src/cmd/render/library.rs`, the generated lookup `library_data.rs` and its generator `tools/render-library/generate.py` project the already attributed starter and SELENE resources without replacing their bytes or authoring manifests. The added scalar texture channels, explicit color/V conventions and immutable-source/GPU-payload sharing are original contributor work under MIT OR Apache-2.0. No external code, commercial CAD data or new third-party imagery was imported. Source metadata and the original authoring attribution remain authoritative; pending runtime acceptance is not a photometric or engineering qualification.
## Physical reference resource additions
| File | Author | Source | Licence |
|---|---|---|---|
| assets/render/physical/bindings.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
| assets/render/physical/exports/coverage.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
| assets/render/physical/exports/native-cards.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
| assets/render/physical/profiles.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
| assets/render/physical/quantities.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
| assets/render/physical/sources.json | SolidCraft contributors | original physical-reference curation, associations or derived schema-1 export; per-fact public sources retained without relicensing their publications | MIT OR Apache-2.0 |
| assets/render/physical/tools/check_physical.py | SolidCraft contributors | original offline physical-reference validation and projection tool | MIT OR Apache-2.0 |
| assets/render/physical/tools/physical.py | SolidCraft contributors | original offline physical-reference validation and projection tool | MIT OR Apache-2.0 |
| assets/render/physical/tools/test_physical.py | SolidCraft contributors | original offline physical-reference validation and projection tool | MIT OR Apache-2.0 |
| assets/render/physical/tools/export_library.py | SolidCraft contributors | original explicit reference-library planner and native no-loss export verifier; no external code | MIT OR Apache-2.0 |
| assets/render/physical/tools/test_export_library.py | SolidCraft contributors | original regressions for native capability refusal, source identity and physical-card preservation | MIT OR Apache-2.0 |
| tools/render-library/selene_studio.py | SolidCraft contributors | original bounded native MCP studio orchestration with pinned editable master, explicit GPU policy and request/export receipts; no generated model or image substitutes | MIT OR Apache-2.0 |
| tools/render-library/selene_recipe.py | SolidCraft contributors | original full-detail SELENE material/light composition and analytic camera fit; unchanged pinned native master, no external imagery or measured optical dataset | MIT OR Apache-2.0 |
| tools/render-library/test_selene_recipe.py | SolidCraft contributors | original independent projection, scaling, nonmutation and input-refusal fixtures for the SELENE studio recipe | MIT OR Apache-2.0 |
| assets/render/selene/scenes/studio-hero.json | SolidCraft contributors | original executable render intent and verified provenance for the selected native SELENE studio showcase; authored RGB appearance and relative light units, not measured optics or simulation results | MIT OR Apache-2.0 |
| tools/render-library/publish_selene.py | SolidCraft contributors | original bounded native-frame publication checks for explicit visual review, source identity, resource hashes, PNG CRCs and scanline limits; copies completed native bytes without image synthesis or resampling | MIT OR Apache-2.0 |
| tools/render-library/test_publish_selene.py | SolidCraft contributors | original tiny analytic PNG fixtures for source-size, CRC, expansion, dimensions and explicit-publication-consent regressions | MIT OR Apache-2.0 |
| tools/render-library/package_selene.py | SolidCraft contributors | original byte-preserving packaging of the reviewed native SELENE PNG, public provenance and checksums; bounded resource/PNG verification, no fonts, executables or new render execution | MIT OR Apache-2.0 |
| .github/workflows/selene-showcase.yml | SolidCraft contributors | original read-only artifact-delivery workflow for the reviewed native SELENE export; existing pinned repository actions, no compilation, package installation, secret use or renderer claims | MIT OR Apache-2.0 |
| tools/render-library/verify_linear_master.py | SolidCraft contributors | original independent validation driver for native linear EXR output using the official OpenEXR Python reader as an external test oracle; no codec implementation copied or production dependency added | MIT OR Apache-2.0 |
## Aging, effects and backgrounds showcase, 2026-10-23
| File | Author | Source | Licence |
|---|---|---|---|
| tools/render-showcase/instrument.py | SolidCraft contributors | original native-scene authoring, checksum-verified editorial assembly or bounded delivery tool; no third-party source or font binary copied | MIT OR Apache-2.0 |
| tools/render-showcase/compose.py | SolidCraft contributors | original native-scene authoring, checksum-verified editorial assembly or bounded delivery tool; no third-party source or font binary copied | MIT OR Apache-2.0 |
| tools/render-showcase/package.py | SolidCraft contributors | original native-scene authoring, checksum-verified editorial assembly or bounded delivery tool; no third-party source or font binary copied | MIT OR Apache-2.0 |
| distribution/SolidCraft-Render-Showcase.zip | SolidCraft contributors and the explicitly credited original model/UI/resource authors | byte-verified selected collaborator package of reviewed native media, editable sample, recipes and public provenance; contained attribution and existing SELENE lettering notices retained; no fonts or executable binaries | MIT OR Apache-2.0 for new contributions; referenced original notices retained |
| distribution/SolidCraft-Render-Showcase.json | SolidCraft contributors | original verified delivery archive manifest with exact SHA-256, member count and bounded scope | MIT OR Apache-2.0 |
+8 -2
View File
@@ -186,8 +186,14 @@ no LTO, and debug assertions off (truck has debug-only assertions that reject va
builds several times faster than `--release`. If a test fails only in a plain debug build, say so
in the PR. `cargo xtask test` builds the tests with it and then runs every test binary at once
(`cargo test` runs them one after another); arguments after `--` go to each binary, e.g.
`cargo xtask test -- shell`. Tests run beside each other on a busy machine, so a test must not
depend on wall-clock timing.
`cargo xtask test -- shell`. Set `SOLIDCRAFT_TEST_JOBS` to a positive integer to opt into a
shared bound on top-level test/side commands for `cargo xtask test` and `cargo xtask ci`.
For example, `SOLIDCRAFT_TEST_JOBS=2` allows two commands at once; the sequential wasm/doc
worker consumes one of those slots. Every binary and side check still runs after failures.
Without the variable the existing concurrency is unchanged. `RUST_TEST_THREADS`,
`ACTION_MATRIX_JOBS` and Cargo's build-job setting remain independent caller overrides;
this limit does not choose their values or change test filters, timeouts or baselines.
Tests run beside each other on a busy machine, so a test must not depend on wall-clock timing.
**Ready pull requests test what they touch.** CI maps changed files to workspace packages and
runs Clippy, the test build and tests for those and their dependents. The
Generated
+95 -98
View File
@@ -590,9 +590,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadmpeg-codec-freecad"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a5257265c2549cf59a8457b3c7aadcc96c95abe20205232553766993a808812"
checksum = "3c4a8fa61748cbb3ce80838b00a8cebfe553460583941f5aba6e2b97c16b4d37"
dependencies = [
"cadmpeg-container",
"cadmpeg-core",
@@ -601,14 +601,14 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
"zip 8.6.0",
"zip",
]
[[package]]
name = "cadmpeg-codec-rhino"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d88b42fb91c7e5af4c75c0433029771f7e3233a0b72ec3d0b2e5ff2c72bed07"
checksum = "d7af41440d4c1d69e88e612de02cc3c9bad79d03429531e640ac924fef53c6bf"
dependencies = [
"cadmpeg-container",
"cadmpeg-core",
@@ -625,53 +625,53 @@ dependencies = [
[[package]]
name = "cadmpeg-codec-step"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83a52258c129a683f77fb360ca30af54ae34339625ad890be12621128a5811c"
checksum = "35e1b6d17cf26b31f59f2edab8ef5c29d2a33216c581fd913f012ad4869bd559"
dependencies = [
"base64",
"cadmpeg-container",
"cadmpeg-core",
"cadmpeg-ir",
"encoding_rs",
"serde",
"serde_json",
"thiserror 2.0.21",
"zip 8.6.0",
"zip",
]
[[package]]
name = "cadmpeg-container"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c110508cd9155e905f52906de505dfd42dc70a0321f5e90b5f68c28ba207686e"
checksum = "5ab7bcef48ac7480b4917820272d85cd2c9533e17904e15b0e4d22f19a6045bd"
dependencies = [
"cadmpeg-core",
"crc32fast",
"flate2",
"zip 8.6.0",
"zstd 0.14.2",
"zip",
]
[[package]]
name = "cadmpeg-core"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b1147e51a74e7c52da5b393a0344597238cdc622fac6443eb2fd526759f6fa"
checksum = "e86bb7a40521832504bb1f5953a1c706b2c52aad73bf6d84e1b35e5cb0beae3f"
dependencies = [
"flate2",
"memchr",
"roxmltree",
"ruzstd",
"serde",
"serde_json",
"thiserror 2.0.21",
"zstd 0.14.2",
]
[[package]]
name = "cadmpeg-ir"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71ff19f019f013d97e353bb802c5d2e253c2859aaa5422009aa933dc2f2e7dbc"
checksum = "b570a1a8c579e407a9fd7bb10fa1900f8827f1d578b8fcb4ea2a056b5f323c72"
dependencies = [
"base64",
"cadmpeg-core",
@@ -684,9 +684,9 @@ dependencies = [
[[package]]
name = "cadmpeg-registry"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66046f1a694926707bea8944f861e699964b04c68972708588f97e14ccee3c5d"
checksum = "b2be23469a9bca0b684f1811118fffb581ebd32ded08e6cc8b0716a41f8c1377"
dependencies = [
"cadmpeg-codec-freecad",
"cadmpeg-codec-rhino",
@@ -996,6 +996,15 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.8"
@@ -2271,6 +2280,16 @@ dependencies = [
"syn 2.0.119",
]
[[package]]
name = "keyboard-types"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d"
dependencies = [
"bitflags 2.13.2",
"serde",
]
[[package]]
name = "keyring"
version = "3.6.3"
@@ -2480,6 +2499,24 @@ dependencies = [
"pxfm",
]
[[package]]
name = "muda"
version = "0.21.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4340e89c178916e62879c5c36b7f699019369be413e496178a6983ce91b8bc03"
dependencies = [
"crossbeam-channel",
"dpi",
"keyboard-types",
"objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-core-foundation",
"objc2-foundation 0.3.2",
"png",
"thiserror 2.0.21",
"windows-sys 0.61.2",
]
[[package]]
name = "multiversion_no_op"
version = "1.0.0"
@@ -3749,6 +3786,15 @@ version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "ruzstd"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69e4ce4a039f42bf25920b95667e4d789e98749ab11bbb8d2c2936f788421bb5"
dependencies = [
"twox-hash",
]
[[package]]
name = "same-file"
version = "1.0.6"
@@ -4162,7 +4208,7 @@ dependencies = [
[[package]]
name = "solidcraft"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"block2 0.6.2",
"eframe",
@@ -4172,6 +4218,7 @@ dependencies = [
"hidapi",
"keyring",
"log",
"muda",
"objc2 0.6.5",
"objc2-app-kit 0.3.2",
"objc2-foundation 0.3.2",
@@ -4193,11 +4240,11 @@ dependencies = [
[[package]]
name = "solidcraft-3dx-client"
version = "0.21.0-rc.1"
version = "0.22.0"
[[package]]
name = "solidcraft-cli"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"image",
"log",
@@ -4209,7 +4256,7 @@ dependencies = [
[[package]]
name = "solidcraft-derive"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"proc-macro2",
"quote",
@@ -4218,7 +4265,7 @@ dependencies = [
[[package]]
name = "solidcraft-doc"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"log",
"serde",
@@ -4235,7 +4282,7 @@ dependencies = [
[[package]]
name = "solidcraft-engine"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"log",
"schemars",
@@ -4254,7 +4301,7 @@ dependencies = [
[[package]]
name = "solidcraft-geom"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"serde",
"serde_json",
@@ -4263,15 +4310,18 @@ dependencies = [
[[package]]
name = "solidcraft-gpu"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"image",
"log",
"naga",
"pollster 0.4.0",
"robust",
"serde",
"serde_json",
"solidcraft-engine",
"solidcraft-geom",
"solidcraft-io",
"solidcraft-kernel",
"solidcraft-render",
"thiserror 2.0.21",
@@ -4280,7 +4330,7 @@ dependencies = [
[[package]]
name = "solidcraft-io"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"cadmpeg-core",
"cadmpeg-ir",
@@ -4298,12 +4348,12 @@ dependencies = [
"solidcraft-geom",
"solidcraft-kernel",
"thiserror 2.0.21",
"zip 9.0.4",
"zip",
]
[[package]]
name = "solidcraft-kernel"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"log",
"proptest",
@@ -4323,7 +4373,7 @@ dependencies = [
[[package]]
name = "solidcraft-mcp"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"serde_json",
"solidcraft-engine",
@@ -4332,7 +4382,7 @@ dependencies = [
[[package]]
name = "solidcraft-render"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"image",
"moxcms",
@@ -4346,7 +4396,7 @@ dependencies = [
[[package]]
name = "solidcraft-sketch"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"serde",
"serde_json",
@@ -4357,14 +4407,14 @@ dependencies = [
[[package]]
name = "solidcraft-space-mouse"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"serde",
]
[[package]]
name = "solidcraft-ui-egui"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"arboard",
"egui",
@@ -4372,6 +4422,7 @@ dependencies = [
"image",
"log",
"minisign-verify",
"nix 0.31.3",
"pollster 0.4.0",
"proc-macro2",
"semver",
@@ -4387,12 +4438,12 @@ dependencies = [
"ttf-parser",
"web-time",
"winreg",
"zip 9.0.4",
"zip",
]
[[package]]
name = "solidcraft-web"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"eframe",
"egui",
@@ -4841,6 +4892,12 @@ version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "twox-hash"
version = "2.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86a801b3cea342a06d468c8710662aa29e5e05e4f5c0d62f00bbb7f2ad7941c2"
[[package]]
name = "type-map"
version = "0.5.1"
@@ -5922,7 +5979,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.21.0-rc.1"
version = "0.22.0"
dependencies = [
"proc-macro2",
"serde_json",
@@ -6215,20 +6272,6 @@ dependencies = [
"syn 3.0.6",
]
[[package]]
name = "zip"
version = "8.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
dependencies = [
"crc32fast",
"flate2",
"indexmap",
"memchr",
"typed-path",
"zstd 0.13.3",
]
[[package]]
name = "zip"
version = "9.0.4"
@@ -6268,52 +6311,6 @@ dependencies = [
"simd-adler32",
]
[[package]]
name = "zstd"
version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
dependencies = [
"zstd-safe 7.3.0",
]
[[package]]
name = "zstd"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "057cfd910cfac363a0ada849592624b4c9ff2e10bef504c3433810d78ed96f93"
dependencies = [
"zstd-safe 8.1.0",
]
[[package]]
name = "zstd-safe"
version = "7.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64d80649ab6db9d9f6f9c80a40becd948eda4714a0a5ac8c4d157a32231c7882"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-safe"
version = "8.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdd44c6a7284e91f3717755b24315a302edd9153a01f753c3cba3d765e8eafac"
dependencies = [
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "2.1.1+zstd.1.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aeec9eaf2dffbbd09201e23bd0ffcbaa33bb8e9266a10734fd7ed90a85eca078"
dependencies = [
"cc",
"pkg-config",
]
[[package]]
name = "zune-core"
version = "0.5.3"
+9 -5
View File
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
exclude = ["vendor"]
[workspace.package]
version = "0.21.0-rc.1"
version = "0.22.0"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -13,6 +13,7 @@ authors = ["SolidCraft contributors"]
repository = "https://github.com/bherbruck/solidcraft"
[workspace.dependencies]
robust = "1.2"
# internal
solidcraft-geom = { path = "crates/geom" }
solidcraft-sketch = { path = "crates/sketch" }
@@ -67,17 +68,20 @@ encoding_rs = "0.8"
# read by crates/io itself, so no image decoding or file access comes with it.
gltf = { version = "1.4", default-features = false, features = ["names"] }
# FreeCAD and Rhino import (Apache-2.0, pure Rust apart from zstd; native builds only).
# FreeCAD and Rhino import (Apache-2.0, pure Rust; native and browser builds).
# Pinned: its IR and codec APIs change between minor versions. Open formats only: no default
# features, so cadmpeg's codecs for commercial formats (f3d, sldprt, inventor, catia, creo, nx)
# and its ShapeManager/ACIS `sat` codec are not compiled in. `step` is the writer we restate through.
cadmpeg-core = "=0.6.1"
cadmpeg-ir = "=0.6.1"
cadmpeg-registry = { version = "=0.6.1", default-features = false, features = ["fcstd", "rhino", "step"] }
cadmpeg-core = "=0.6.3"
cadmpeg-ir = "=0.6.3"
cadmpeg-registry = { version = "=0.6.3", default-features = false, features = ["fcstd", "rhino", "step"] }
# fonts (sketch text outlines; MIT OR Apache-2.0, pure Rust)
ttf-parser = { version = "0.25", default-features = false, features = ["std"] }
# Ends a process group with the system call (MIT; already in the tree through zip).
nix = { version = "0.31", default-features = false, features = ["signal"] }
# images
image = { version = "0.25", default-features = false, features = ["png", "hdr"] }
png = "0.18.1"
+18 -4
View File
@@ -51,24 +51,28 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
|---|---|---|---|
| M0 | Vertical slice | done | — |
| M1 | Sketch depth: command coverage implemented — projection/intersect/include as linked geometry (auto-project on faces, lost-reference warnings, break link), full SKETCH create/modify set (slots, polygons, tangent arcs/circles, ellipse, fit/control splines, conic, text, trim/extend/break, fillet/chamfer, offset, mirror, patterns, move/scale, blend curve, centerline), constraints incl. curvature and polygon with over-constraint refusal, Shift-picked line midpoints in Horizontal/Vertical using persisted points and ordinary constraints (#2087), driven/arc-length/linear-diameter dimensions, AutoConstrain, DXF/SVG insert, 3D sketch curves (Project To Surface, Intersection Curve, Isoparametric Curve, Spun Profile), exact free-form profiles (B-spline/NURBS edges, any spline degree, curves cut by crossings), sweeps and pipes along 3D sketch curves, canvases and decals, curvature combs. Exact free-form Extend and certified associative offsets now preserve geometry and IDs through edits, deletion and reopen. Occurrence-frame 3D wires now store explicit coordinate spaces and retain points and analytic fits through moves, refresh, undo and reopen. Constructive sketch solving (Owen 1991, Fudos & Hoffmann 1997) ahead of the numerical solver: points and rigid clusters are placed on the branch the sketch had, so dimension edits and drags keep their solution; edits that would turn a sketch over are refused; drags re-solve only the sketch per frame. Remaining workflow repairs: mixed offset junctions, legacy natural-spline seams, and damaged legacy wire links lacking frame provenance | in progress | 4 |
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces, at distances from reference edges, and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
| M3 | General fillets and chamfers (curved edges, chains, vertex blends); saddle rolling-ball surfaces now use continuous seam sections and arc-length fitting with shared contact boundaries and bounded accuracy checks; tiny rim edges remain selected instead of nearby wall seams when filleting (#2498) | in progress | 30 |
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), FreeCAD/Rhino solids through cadmpeg (done, desktop and CLI), 3MF export and 3MF/STL import as mesh bodies (done), 3MF with B-rep face tags ([docs/formats/3mf-face-tags.md](docs/formats/3mf-face-tags.md), done), constant-section parametric mesh reconstruction, DXF sketches, OBJ import | in progress | 6 |
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), FreeCAD/Rhino solids through cadmpeg (done, desktop, browser and CLI), 3MF export and 3MF/STL import as mesh bodies (done), 3MF with B-rep face tags ([docs/formats/3mf-face-tags.md](docs/formats/3mf-face-tags.md), done), constant-section parametric mesh reconstruction, DXF sketches, OBJ import | in progress | 6 |
| M5 | Persistent naming of faces and edges (docs/book/src/dev/naming.md): faces named from their exact kernel-reported history (sketch curve sides, caps, revolve sections, primitive sides, blends, shell inner walls, pattern/mirror copies; split pieces told apart by the faces around them, lumps of a cut body named after their faces), edges by their faces; fillets/chamfers, sketches on faces, shell/draft/offset/replace faces, hole placements, joint origins and face appearances resolve by name first and warn when the entity split or vanished: done. sheet metal edges by base sketch line and flange side, shell inner faces, sweep and loft roles: done. kernel-reported face history: done (merged faces answer to every name; operations without history yet fall back to geometric names, flagged unverified; format 14 keeps older designs' geometric names). Left: history through sheet metal, plastic, surface and emboss features; FaceSource certificates through history | in progress | 20 |
| M6 | UI depth: selection feedback and origin (done), timeline editing (done: hover and selection highlight modifier faces, including fillets and chamfers; edit feature, history marker drag, dependency-checked single and multi-item reorder, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names; responsive feature history with width-driven icons/names/title, snapping width resize, bottom/left/browser-stack docking, shared-column collapse, rollback bar, multi-selection and persistent folders), right-docked dialogs (done), live previews for every feature dialog (done), keyboard-first commands with on-canvas value boxes and sketch inline dimensions (done), drag manipulators with automatic join/cut (done: extrude incl. two sides, fillet, shell, hole depth, move, pattern spacing, revolve rotator, extrude taper), dark theme (done), right-click menu with Repeat (done), measure tool (done), section analysis (done), selection filter (done), drag-solve of sketch points (done); appearance controls now preserve body/face/component targeting, named transparent looks, reuse/search and override removal; preferences | in progress | 6 |
| M6 | UI depth: selection feedback and origin (done), timeline editing (done: hover and selection highlight modifier faces, including fillets and chamfers; edit feature, history marker drag, dependency-checked single and multi-item reorder, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names; responsive feature history with width-driven icons/names/title, snapping width resize, bottom/left/browser-stack docking, shared-column collapse, rollback bar, multi-selection and persistent folders), right-docked dialogs (done), live previews for every feature dialog (done), keyboard-first commands with on-canvas value boxes and sketch inline dimensions (done), drag manipulators with automatic join/cut (done: extrude incl. two sides, fillet, shell, hole depth, move, pattern spacing, revolve rotator, extrude taper), dark theme (done), right-click menu with Repeat (done), measure tool (done), section analysis (done), selection filter (done), drag-solve of sketch points (done); appearance controls now preserve body/face/component targeting, named transparent looks, reuse/search and override removal; Browser Expand All / Collapse All (`browser.expand_all`, `browser.collapse_all`) on the design and component menus; preferences | in progress | 6 |
| M7 | Thin extrude (walls along open or closed curves: centre, inside, outside or two sides; mitred corners, crossing walls joined; #721), sweep, loft (incl. to a point), rib and web (to next / depth), emboss (planar faces and exact radial emboss/deboss of bounded orthogonal profiles on complete exterior cylinders/sleeves), thread (cosmetic, and modelled M3–M20 outside and inside: the ISO profile screwed in as a faceted cut), coil (smooth B-spline helical walls), pipe, pattern on path, patterns of bodies and components, replace face (parallel), align, remove, feature copy/paste: done. Unique eligible cylindrical targets are inferred for new Emboss features. Left: general curved-face emboss and exact (unfaceted) modelled threads | in progress | 12 |
| M8 | Surface workspace (#772). Done: Patch (sketch regions; edge loops filled G0, plus `crown`), Stitch, Thicken (planes and cylinders), Extend (straight edges of planar surfaces), Trim (by a plane, a face or sketch curves swept onto the surface, keeping a side or the picked regions; mutual trim of two surfaces, extended to meet and stitched, #767), Delete Face (healing), Boundary Fill, zebra/draft/curvature-map analysis, Surface Extrude/Revolve/Sweep/Loft from open and closed curves, Offset, Unstitch and Reverse Normal; the continuity-driven core (`kernel/src/bridge.rs`, G0–G3 Hermite rows) with **Blend** (`surface.blend`, #764: edge chains along one or several faces, closed rims paired seam to seam or from picked seams, a dialog), **Match** (`surface.match`), **Boundary** (`surface.boundary`, #763 — two sketch curves exact with G0–G3, three or four as the flat patch of their loop, and curve networks: Coons and Gordon surfaces through sketch, 3D-sketch or edge curves, a point for a three-sided side, G1/G2 against the faces along one direction's edges, joined into their body on request; G1/G2 on two sides that meet at a corner waits for the Gregory-type corner), **Fair** (`surface.fair`, CV fairing), **continuity analysis** (`inspect.continuity`, #770; a toolbar tool with edge markers and a legend, and curvature combs along face isocurves) and a multi-section **loft** with per-end continuity and crown, and guide rails on solid and surface lofts (one or two, scaling or stretching the sections between; #201). Also in the Class-A pass: G1…G4 blend curves in the sketch, chord and variable-radius fillets on curved edges (with honest refusals where the fit cannot hold), and a G2 corner patch where filleted edges meet (kernel; the command refuses until the patch's sections can be sewn to the blends' contacts). Left: bias/form-factor fillet sections (design recorded), a Gregory-type corner, surface Fillet between separate surface bodies (#769), Ruled (#762), Untrim (#761), curved-face Extend (#760), and every remaining #757–#772 item | deferred | — |
| M9 | Components and assemblies: New Component / Create Components from Bodies, active component, per-component origin/bodies/sketches/timeline entries, occurrences with transforms, Copy/Paste vs Paste New, nested Browser tree with visibility and isolate; Ground, Move/Copy of occurrences, joints (rigid, revolute, slider, cylindrical, pin-slot, planar, ball) by joint origins with limits and Drive Joints, closed kinematic loops (four-bar, slider-crank: the loop's other joints follow the driven one on its branch, and driving stops where a loop can no longer close), cams (`joint.cam`: a revolute cam drives a slider or revolute follower along a closed sketch profile or the cam's edges, knife, roller or flat followers, worked out again when the profile changes; drives, studies, closed loops, contact sets), As-built Joint, Rigid Group, Contact Sets (driving and motion studies stop where bodies in a set would pass through each other), Motion Study (joint values keyed along a timeline, playback, export of occurrence positions as JSON/CSV), motion analysis (joint values and a point's path charted over a study as value, velocity and acceleration; the parts that collide along a study, with the steps where they do), Exploded views (automatic or by hand, shown without changing placements); interference, per-component physical properties; STEP with assembly structure; URDF export for robot simulators (links with meshes, mass and inertia; joint chains whose values match Drive Joints; docs/urdf.md). Physical properties now measure material-aware component/occurrence placements, descendants and repeated instances, including pending moves and body reassignment offsets; contact geometry and automatic exploded centres respect those offsets. Groundwork done: component tree, occurrences with transforms (instances, Paste New), world placement, active component as authoring frame, ground, Capture/Revert Position, moving bodies and sketches. UI: ASSEMBLE panel dialogs for New Component, Joint (snaps on faces, edges, circles and vertices; animated motion preview; offsets, flip, limits), As-built Joint, Joint Origin, Rigid Group, Drive Joints (live slider), Motion Link, Cam and Interference (overlaps shown red); joints in the timeline and the Browser | in progress | 32 |
| M9 | Components and assemblies: New Component / Create Components from Bodies, active component, per-component origin/bodies/sketches/timeline entries, occurrences with transforms, Copy/Paste vs Paste New, nested Browser tree with visibility and isolate; Ground, Move/Copy of occurrences, joints (rigid, revolute, slider, cylindrical, pin-slot, planar, ball) by joint origins with limits and Drive Joints, closed kinematic loops (four-bar, slider-crank: the loop's other joints follow the driven one on its branch, and driving stops where a loop can no longer close), cams (`joint.cam`: a revolute cam drives a slider or revolute follower along a closed sketch profile or the cam's edges, knife, roller or flat followers, worked out again when the profile changes; drives, studies, closed loops, contact sets), As-built Joint, Rigid Group, Contact Sets (driving and motion studies stop where bodies in a set would pass through each other), Motion Study (joint values keyed along a timeline, playback, export of occurrence positions as JSON/CSV), motion analysis (joint values and a point's path charted over a study as value, velocity and acceleration; the parts that collide along a study, with the steps where they do), Exploded views (automatic or by hand, shown without changing placements, and exported as an animation coming apart and back together); interference, per-component physical properties; STEP with assembly structure; URDF export for robot simulators (links with meshes, mass and inertia; joint chains whose values match Drive Joints; docs/urdf.md). Physical properties now measure material-aware component/occurrence placements, descendants and repeated instances, including pending moves and body reassignment offsets; contact geometry and automatic exploded centres respect those offsets. Groundwork done: component tree, occurrences with transforms (instances, Paste New), world placement, active component as authoring frame, ground, Capture/Revert Position, moving bodies and sketches. UI: ASSEMBLE panel dialogs for New Component, Joint (snaps on faces, edges, circles and vertices; animated motion preview; offsets, flip, limits), As-built Joint, Joint Origin, Rigid Group, Drive Joints (live slider), Motion Link, Cam and Interference (overlaps shown red); joints in the timeline and the Browser | in progress | 32 |
| M10 | Sheet metal: flange (base, edge, contour), hem, unfold/refold, convert, flat pattern + DXF, rules: done. PLASTIC enclosure subset: Boss, Rib, Web, Lip/Groove, Snap Fit, Rest, Plastic Rules (library, edit, assign; rule draft and clearance as feature defaults): done, tested against hand-computed geometry. UI: SHEET METAL and PLASTIC tabs with dialogs (live preview, Edit Feature, pre-selection) for Flange, Hem, Unfold/Refold, Convert, Sheet Metal Rules, Create Flat Pattern (flat view with bend lines, bend table CSV/JSON and DXF export with exact circle/arc/line cutouts), Boss, Lip/Groove, Snap Fit, Rest, Manage and Assign Plastic Rules; Fold (bend along a sketch line); flanges meeting at a corner closed at any angle with the rule's gap; edge flanges with their own angle and height per edge in one feature; corner reliefs (notch, round, rectangular, tear) chosen by the rule; Convert to Sheet Metal from a solid or shelled body by its bend edges (flat faces, sharp bends) | in progress | 36 |
| M11 | 2D drawings | deferred | — |
| M12 | Release: cross-platform packages, verification and deliberate publication implemented; original current-UI demo refreshed. Trusted distribution signing and broader performance work remain | in progress | 16 |
| M14 | Configurations: a table of rows (parameter expressions and feature suppressions per row) switched with config.activate (undoable, all or nothing), kept through parameter renames and feature deletes (config.configure, config.table, config.column/row/activate). Per-row body materials and appearances (library looks, custom colour/opacity and clearing overrides), undoable activation and save/reopen: done. Ordered typed configuration rules, atomic validation and structured rule editing: done. Component appearance and certified semantic face style columns, atomic preview/apply and independent-copy remapping: done. Exact constructed sketch-side and immutable imported-face lineage now preserve legacy names and reject retargeting. Left: ambiguous shifted/two-sided caps | in progress | 4 |
| M13 | Never lose work: atomic saves (temporary file, flush, rename; a crash at any moment leaves the old file or the new one) with the replaced version kept as `<file>.bak`; autosave of unsaved changes to the recovery folder every 5 minutes (preference) and after big operations, only when something changed; "Recover unsaved design?" on the next launch (`doc.recovery_list`, `doc.recover`, `doc.recovery_discard`); a kill-mid-save test (process killed while saving a 0.5 MB design 25 times). File format: `"format": "solidcraft/12"`, older formats upgraded step by step (`doc::format`), newer ones refused with a message, out-of-range numbers and broken placements refused; a corpus of designs saved by M0, components-era, joints-era and current builds (apps/solidcraft-cli/tests/corpus) that must open and evaluate as saved; damaged and hostile variants (truncation, bit flips, hostile values, removed keys, repeated features, deep nesting) never panic | done | 6 |
| M15 | Frames and weldments (#4003): structural members along 2D and 3D sketch lines, butt and mitre corners, trim to faces and bodies, end caps, gussets, cosmetic welds and a cut list. Done: the profile library (`frame.profile.list`, `frame.profile.properties`, `frame.profile.insert`): bar, round bar, rectangular and round tube, angle, channel and I-beam, each SolidCraft's own parametric definition with exact fillet arcs and exact section properties (area, centroid, second moments), tested against hand-computed values; members (`frame.member`): one body per straight line of a 2D or 3D frame sketch, following parameter edits, with a built-in profile (its dimensions parameter expressions) or a profile sketch (its drawn material by alternating nested loops), anchor, angle and mirror; each member's name kept with its line. Left: corners, trims, end caps, gussets, welds, cut list, FRAME tab and dialogs | in progress | 60 |
M6 navigation preferences include **Touchpad**: Shift + pointer movement pans, Alt/Option +
pointer movement orbits without a mouse button, and vertical scrolling zooms. Other presets
retain their saved two-finger scroll preference; camera motion is available through
`view.navigate` and modifier-aware `ui.move` automation.
With the **Fusion** preset and two-finger pan enabled, two fingers pan and Shift + two fingers
orbit freely, with pinch zoom and mouse bindings unchanged. The shared navigation handler
applies the same gestures to model and Render views and blocks orbit on flat pages.
UI redesign (#2016): the File/start view (#2027) uses shared light/dark navigation,
action tiles, recent rows and sample cards, with recovery review and Save As/Export
@@ -139,6 +143,12 @@ completed drawings publish atomically. The browser still exports synchronously.
## Render foundation
- 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).
- Definition-local visual mapping (#1214) supplies a bounded numerical frame, physical repeat coordinates and a deterministic one-time grain-axis suggestion. It adds no saved record or texture consumer; see [the mapping contract](docs/render-visual-mapping.md).
- Explicit texture normalization (#1218) produces completed float RGBA with
role-aware linear color/data semantics, bounded matrix/TRC ICC conversion and
opt-in EXIF orientation. It retains high-precision raw samples and records lossy
@@ -583,6 +593,10 @@ completed drawings publish atomically. The browser still exports synchronously.
continuous collision detection; local thin walls and small details remain limitations.
See [contact-set limits and recovery](docs/contact-sets.md).
## Assembly dragging
The design's first part is grounded when it is made, and a left-button drag repositions a free part (`occurrence.drag`: one undo step, exact occurrence paths including repeated jointed definitions, any floating-chain member carries the chain, grounded parts and joints hold where present, Escape cancels).
## 3D sketch bridges
3D sketch drawing snaps to points on other visible sketch planes without flattening them onto
+767 -126
View File
@@ -1,6 +1,6 @@
Third-party software in SolidCraft
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 560 crates below.
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 559 crates below.
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
@@ -60,13 +60,13 @@ bytemuck_derive 1.12.1 (Zlib OR Apache-2.0 OR MIT)
byteorder 1.5.0 (Unlicense OR MIT)
byteorder-lite 0.1.0 (Unlicense OR MIT)
bytes 1.12.1 (MIT)
cadmpeg-codec-freecad 0.6.1 (Apache-2.0)
cadmpeg-codec-rhino 0.6.1 (Apache-2.0)
cadmpeg-codec-step 0.6.1 (Apache-2.0)
cadmpeg-container 0.6.1 (Apache-2.0)
cadmpeg-core 0.6.1 (Apache-2.0)
cadmpeg-ir 0.6.1 (Apache-2.0)
cadmpeg-registry 0.6.1 (Apache-2.0)
cadmpeg-codec-freecad 0.6.3 (Apache-2.0)
cadmpeg-codec-rhino 0.6.3 (Apache-2.0)
cadmpeg-codec-step 0.6.3 (Apache-2.0)
cadmpeg-container 0.6.3 (Apache-2.0)
cadmpeg-core 0.6.3 (Apache-2.0)
cadmpeg-ir 0.6.3 (Apache-2.0)
cadmpeg-registry 0.6.3 (Apache-2.0)
calloop 0.13.0 (MIT)
calloop 0.14.5 (MIT)
calloop-wayland-source 0.3.0 (MIT)
@@ -95,6 +95,7 @@ core_detect 1.0.0 (MIT/Apache-2.0)
cpufeatures 0.2.17 (MIT OR Apache-2.0)
cpufeatures 0.3.1 (MIT OR Apache-2.0)
crc32fast 1.5.2 (MIT OR Apache-2.0)
crossbeam-channel 0.5.17 (MIT OR Apache-2.0)
crossbeam-deque 0.8.8 (MIT OR Apache-2.0)
crossbeam-epoch 0.9.21 (MIT OR Apache-2.0)
crossbeam-utils 0.8.23 (MIT OR Apache-2.0)
@@ -210,6 +211,7 @@ jni-sys 0.4.1 (MIT OR Apache-2.0)
jni-sys-macros 0.4.1 (MIT OR Apache-2.0)
js-sys 0.3.106 (MIT OR Apache-2.0)
katexit 0.1.5 (MIT OR Apache-2.0)
keyboard-types 0.8.3 (MIT OR Apache-2.0)
keyring 3.6.3 (MIT OR Apache-2.0)
khronos-egl 6.0.0 (MIT/Apache-2.0)
kurbo 0.13.1 (Apache-2.0 OR MIT)
@@ -234,6 +236,7 @@ minisign-verify 0.3.0 (MIT)
miniz_oxide 0.8.9 (MIT OR Zlib OR Apache-2.0)
miniz_oxide 0.9.1 (MIT OR Zlib OR Apache-2.0)
moxcms 0.8.1 (BSD-3-Clause OR Apache-2.0)
muda 0.21.2 (Apache-2.0 OR MIT)
multiversion_no_op 1.0.0 (Apache-2.0 OR MIT)
naga 30.0.1 (MIT OR Apache-2.0)
naga-types 30.0.1 (MIT OR Apache-2.0)
@@ -352,6 +355,7 @@ rustls-platform-verifier 0.7.1 (MIT OR Apache-2.0)
rustls-platform-verifier-android 0.2.0 (MIT OR Apache-2.0)
rustls-webpki 0.103.15 (ISC)
rustversion 1.0.23 (MIT OR Apache-2.0)
ruzstd 0.9.1 (MIT)
schannel 0.1.29 (MIT)
schemars 1.2.3 (MIT)
schemars_derive 1.2.3 (MIT)
@@ -429,6 +433,7 @@ truck-shapeops 0.4.0 (Apache-2.0)
truck-stepio 0.3.0 (Apache-2.0)
truck-topology 0.6.0 (Apache-2.0)
ttf-parser 0.25.1 (MIT OR Apache-2.0)
twox-hash 2.1.5 (MIT)
type-map 0.5.1 (MIT/Apache-2.0)
typed-path 0.12.3 (MIT OR Apache-2.0)
typeid 1.0.3 (MIT OR Apache-2.0)
@@ -546,16 +551,10 @@ zeroize_derive 1.5.0 (Apache-2.0 OR MIT)
zerotrie 0.2.5 (Unicode-3.0)
zerovec 0.11.8 (Unicode-3.0)
zerovec-derive 0.11.6 (Unicode-3.0)
zip 8.6.0 (MIT)
zip 9.0.4 (MIT)
zlib-rs 0.6.8 (Zlib)
zmij 1.0.23 (MIT)
zopfli 0.8.3 (Apache-2.0)
zstd 0.13.3 (MIT)
zstd 0.14.2 (BSD-3-Clause)
zstd-safe 7.3.0 (BSD-3-Clause)
zstd-safe 8.1.0 (BSD-3-Clause)
zstd-sys 2.1.1+zstd.1.5.7 (BSD-3-Clause)
zune-core 0.5.3 (MIT OR Apache-2.0 OR Zlib)
zune-jpeg 0.5.15 (MIT OR Apache-2.0 OR Zlib)
zvariant 4.2.0 (MIT)
@@ -1108,26 +1107,27 @@ ahash 0.8.12, arrayvec 0.7.8, async-channel 2.5.0, async-executor 1.14.0, async-
4.7.1, atomic-waker 1.1.2, base64 0.23.1, bitflags 1.3.2, bitflags 2.13.2, blocking 1.7.0, bumpalo
3.20.3, cfg-if 1.0.5, cgl 0.3.2, concurrent-queue 2.5.0, core-foundation 0.10.1, core-foundation
0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, core_detect
1.0.0, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23, dbus-secret-service
4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14, euclid 0.22.14,
event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10, form_urlencoded
1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow 0.17.0, gltf 1.4.1, gltf-derive
1.4.1, gltf-json 1.4.1, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, httparse 1.10.1, idna
1.1.0, idna_adapter 1.2.2, indexmap 2.14.2, itertools 0.13.0, itertools 0.15.0, js-sys 0.3.106,
khronos-egl 6.0.0, lazy_static 1.5.1, linux-raw-sys 0.12.1, linux-raw-sys 0.4.15, lock_api 0.4.14,
log 0.4.34, num 0.4.3, num-bigint 0.4.8, num-complex 0.4.6, num-integer 0.1.47, num-iter 0.1.46,
num-rational 0.4.2, num-traits 0.2.19, once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0,
parking 2.2.1, parking_lot 0.12.5, parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5,
plain 0.2.3, png 0.18.1, polling 3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core
1.13.0, renderdoc-sys 1.1.0, robust 1.2.0, ron 0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix
0.38.44, rustix 1.1.5, rustls 0.23.46, rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard
1.2.0, secret-service 4.0.0, security-framework 2.11.1, security-framework 3.7.0,
security-framework-sys 2.17.0, signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.16.2,
smol_str 0.2.2, stable_deref_trait 1.2.1, syn 1.0.109, tempfile 3.27.0, ttf-parser 0.25.1,
unicode-segmentation 1.13.3, unicode-width 0.2.2, url 2.5.8, uuid 1.27.0, wasi
0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, wasm-bindgen-futures
0.4.79, wasm-bindgen-macro 0.2.129, wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129,
web-sys 0.3.106, weezl 0.1.12, wit-bindgen 0.57.1
1.0.0, crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils
0.8.23, dbus-secret-service 4.1.0, displaydoc 0.2.7, either 1.19.0, equivalent 1.0.2, errno 0.3.14,
euclid 0.22.14, event-listener 5.4.2, event-listener-strategy 0.5.4, fastrand 2.5.0, flate2 1.1.10,
form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow 0.17.0, gltf 1.4.1,
gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.16.1, hashbrown 0.17.1, hermit-abi 0.5.3, httparse
1.10.1, idna 1.1.0, idna_adapter 1.2.2, indexmap 2.14.2, itertools 0.13.0, itertools 0.15.0, js-sys
0.3.106, keyboard-types 0.8.3, khronos-egl 6.0.0, lazy_static 1.5.1, linux-raw-sys 0.12.1,
linux-raw-sys 0.4.15, lock_api 0.4.14, log 0.4.34, muda 0.21.2, num 0.4.3, num-bigint 0.4.8,
num-complex 0.4.6, num-integer 0.1.47, num-iter 0.1.46, num-rational 0.4.2, num-traits 0.2.19,
once_cell 1.21.4, openssl-probe 0.2.1, ordered-stream 0.2.0, parking 2.2.1, parking_lot 0.12.5,
parking_lot_core 0.9.12, percent-encoding 2.3.2, piper 0.2.5, plain 0.2.3, png 0.18.1, polling
3.11.0, pollster 0.4.0, pollster 1.0.1, rayon 1.12.0, rayon-core 1.13.0, renderdoc-sys 1.1.0, robust
1.2.0, ron 0.12.2, roxmltree 0.21.1, rustc-hash 1.1.0, rustix 0.38.44, rustix 1.1.5, rustls 0.23.46,
rustls-native-certs 0.8.5, scoped-tls 1.0.1, scopeguard 1.2.0, secret-service 4.0.0,
security-framework 2.11.1, security-framework 3.7.0, security-framework-sys 2.17.0,
signal-hook-registry 1.4.8, simd_cesu8 1.2.0, smallvec 1.16.2, smol_str 0.2.2, stable_deref_trait
1.2.1, syn 1.0.109, tempfile 3.27.0, ttf-parser 0.25.1, unicode-segmentation 1.13.3, unicode-width
0.2.2, url 2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12,
wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129,
wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12,
wit-bindgen 0.57.1
================================================================================
Apache License
@@ -4030,7 +4030,605 @@ Apache License
limitations under the License.
================================================================================
crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23
crossbeam-channel 0.5.17
================================================================================
===============================================================================
matching.go
https://creativecommons.org/licenses/by/3.0/legalcode
Creative Commons Legal Code
Attribution 3.0 Unported
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR
DAMAGES RESULTING FROM ITS USE.
License
THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE
COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY
COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS
AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE
TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY
BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS
CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND
CONDITIONS.
1. Definitions
a. "Adaptation" means a work based upon the Work, or upon the Work and
other pre-existing works, such as a translation, adaptation,
derivative work, arrangement of music or other alterations of a
literary or artistic work, or phonogram or performance and includes
cinematographic adaptations or any other form in which the Work may be
recast, transformed, or adapted including in any form recognizably
derived from the original, except that a work that constitutes a
Collection will not be considered an Adaptation for the purpose of
this License. For the avoidance of doubt, where the Work is a musical
work, performance or phonogram, the synchronization of the Work in
timed-relation with a moving image ("synching") will be considered an
Adaptation for the purpose of this License.
b. "Collection" means a collection of literary or artistic works, such as
encyclopedias and anthologies, or performances, phonograms or
broadcasts, or other works or subject matter other than works listed
in Section 1(f) below, which, by reason of the selection and
arrangement of their contents, constitute intellectual creations, in
which the Work is included in its entirety in unmodified form along
with one or more other contributions, each constituting separate and
independent works in themselves, which together are assembled into a
collective whole. A work that constitutes a Collection will not be
considered an Adaptation (as defined above) for the purposes of this
License.
c. "Distribute" means to make available to the public the original and
copies of the Work or Adaptation, as appropriate, through sale or
other transfer of ownership.
d. "Licensor" means the individual, individuals, entity or entities that
offer(s) the Work under the terms of this License.
e. "Original Author" means, in the case of a literary or artistic work,
the individual, individuals, entity or entities who created the Work
or if no individual or entity can be identified, the publisher; and in
addition (i) in the case of a performance the actors, singers,
musicians, dancers, and other persons who act, sing, deliver, declaim,
play in, interpret or otherwise perform literary or artistic works or
expressions of folklore; (ii) in the case of a phonogram the producer
being the person or legal entity who first fixes the sounds of a
performance or other sounds; and, (iii) in the case of broadcasts, the
organization that transmits the broadcast.
f. "Work" means the literary and/or artistic work offered under the terms
of this License including without limitation any production in the
literary, scientific and artistic domain, whatever may be the mode or
form of its expression including digital form, such as a book,
pamphlet and other writing; a lecture, address, sermon or other work
of the same nature; a dramatic or dramatico-musical work; a
choreographic work or entertainment in dumb show; a musical
composition with or without words; a cinematographic work to which are
assimilated works expressed by a process analogous to cinematography;
a work of drawing, painting, architecture, sculpture, engraving or
lithography; a photographic work to which are assimilated works
expressed by a process analogous to photography; a work of applied
art; an illustration, map, plan, sketch or three-dimensional work
relative to geography, topography, architecture or science; a
performance; a broadcast; a phonogram; a compilation of data to the
extent it is protected as a copyrightable work; or a work performed by
a variety or circus performer to the extent it is not otherwise
considered a literary or artistic work.
g. "You" means an individual or entity exercising rights under this
License who has not previously violated the terms of this License with
respect to the Work, or who has received express permission from the
Licensor to exercise rights under this License despite a previous
violation.
h. "Publicly Perform" means to perform public recitations of the Work and
to communicate to the public those public recitations, by any means or
process, including by wire or wireless means or public digital
performances; to make available to the public Works in such a way that
members of the public may access these Works from a place and at a
place individually chosen by them; to perform the Work to the public
by any means or process and the communication to the public of the
performances of the Work, including by public digital performance; to
broadcast and rebroadcast the Work by any means including signs,
sounds or images.
i. "Reproduce" means to make copies of the Work by any means including
without limitation by sound or visual recordings and the right of
fixation and reproducing fixations of the Work, including storage of a
protected performance or phonogram in digital form or other electronic
medium.
2. Fair Dealing Rights. Nothing in this License is intended to reduce,
limit, or restrict any uses free from copyright or rights arising from
limitations or exceptions that are provided for in connection with the
copyright protection under copyright law or other applicable laws.
3. License Grant. Subject to the terms and conditions of this License,
Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
perpetual (for the duration of the applicable copyright) license to
exercise the rights in the Work as stated below:
a. to Reproduce the Work, to incorporate the Work into one or more
Collections, and to Reproduce the Work as incorporated in the
Collections;
b. to create and Reproduce Adaptations provided that any such Adaptation,
including any translation in any medium, takes reasonable steps to
clearly label, demarcate or otherwise identify that changes were made
to the original Work. For example, a translation could be marked "The
original work was translated from English to Spanish," or a
modification could indicate "The original work has been modified.";
c. to Distribute and Publicly Perform the Work including as incorporated
in Collections; and,
d. to Distribute and Publicly Perform Adaptations.
e. For the avoidance of doubt:
i. Non-waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme cannot be waived, the Licensor
reserves the exclusive right to collect such royalties for any
exercise by You of the rights granted under this License;
ii. Waivable Compulsory License Schemes. In those jurisdictions in
which the right to collect royalties through any statutory or
compulsory licensing scheme can be waived, the Licensor waives the
exclusive right to collect such royalties for any exercise by You
of the rights granted under this License; and,
iii. Voluntary License Schemes. The Licensor waives the right to
collect royalties, whether individually or, in the event that the
Licensor is a member of a collecting society that administers
voluntary licensing schemes, via that society, from any exercise
by You of the rights granted under this License.
The above rights may be exercised in all media and formats whether now
known or hereafter devised. The above rights include the right to make
such modifications as are technically necessary to exercise the rights in
other media and formats. Subject to Section 8(f), all rights not expressly
granted by Licensor are hereby reserved.
4. Restrictions. The license granted in Section 3 above is expressly made
subject to and limited by the following restrictions:
a. You may Distribute or Publicly Perform the Work only under the terms
of this License. You must include a copy of, or the Uniform Resource
Identifier (URI) for, this License with every copy of the Work You
Distribute or Publicly Perform. You may not offer or impose any terms
on the Work that restrict the terms of this License or the ability of
the recipient of the Work to exercise the rights granted to that
recipient under the terms of the License. You may not sublicense the
Work. You must keep intact all notices that refer to this License and
to the disclaimer of warranties with every copy of the Work You
Distribute or Publicly Perform. When You Distribute or Publicly
Perform the Work, You may not impose any effective technological
measures on the Work that restrict the ability of a recipient of the
Work from You to exercise the rights granted to that recipient under
the terms of the License. This Section 4(a) applies to the Work as
incorporated in a Collection, but this does not require the Collection
apart from the Work itself to be made subject to the terms of this
License. If You create a Collection, upon notice from any Licensor You
must, to the extent practicable, remove from the Collection any credit
as required by Section 4(b), as requested. If You create an
Adaptation, upon notice from any Licensor You must, to the extent
practicable, remove from the Adaptation any credit as required by
Section 4(b), as requested.
b. If You Distribute, or Publicly Perform the Work or any Adaptations or
Collections, You must, unless a request has been made pursuant to
Section 4(a), keep intact all copyright notices for the Work and
provide, reasonable to the medium or means You are utilizing: (i) the
name of the Original Author (or pseudonym, if applicable) if supplied,
and/or if the Original Author and/or Licensor designate another party
or parties (e.g., a sponsor institute, publishing entity, journal) for
attribution ("Attribution Parties") in Licensor's copyright notice,
terms of service or by other reasonable means, the name of such party
or parties; (ii) the title of the Work if supplied; (iii) to the
extent reasonably practicable, the URI, if any, that Licensor
specifies to be associated with the Work, unless such URI does not
refer to the copyright notice or licensing information for the Work;
and (iv) , consistent with Section 3(b), in the case of an Adaptation,
a credit identifying the use of the Work in the Adaptation (e.g.,
"French translation of the Work by Original Author," or "Screenplay
based on original Work by Original Author"). The credit required by
this Section 4 (b) may be implemented in any reasonable manner;
provided, however, that in the case of a Adaptation or Collection, at
a minimum such credit will appear, if a credit for all contributing
authors of the Adaptation or Collection appears, then as part of these
credits and in a manner at least as prominent as the credits for the
other contributing authors. For the avoidance of doubt, You may only
use the credit required by this Section for the purpose of attribution
in the manner set out above and, by exercising Your rights under this
License, You may not implicitly or explicitly assert or imply any
connection with, sponsorship or endorsement by the Original Author,
Licensor and/or Attribution Parties, as appropriate, of You or Your
use of the Work, without the separate, express prior written
permission of the Original Author, Licensor and/or Attribution
Parties.
c. Except as otherwise agreed in writing by the Licensor or as may be
otherwise permitted by applicable law, if You Reproduce, Distribute or
Publicly Perform the Work either by itself or as part of any
Adaptations or Collections, You must not distort, mutilate, modify or
take other derogatory action in relation to the Work which would be
prejudicial to the Original Author's honor or reputation. Licensor
agrees that in those jurisdictions (e.g. Japan), in which any exercise
of the right granted in Section 3(b) of this License (the right to
make Adaptations) would be deemed to be a distortion, mutilation,
modification or other derogatory action prejudicial to the Original
Author's honor and reputation, the Licensor will waive or not assert,
as appropriate, this Section, to the fullest extent permitted by the
applicable national law, to enable You to reasonably exercise Your
right under Section 3(b) of this License (right to make Adaptations)
but not otherwise.
5. Representations, Warranties and Disclaimer
UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR
OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY
KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE,
INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY,
FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF
LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS,
WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION
OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE
LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR
ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES
ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS
BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
7. Termination
a. This License and the rights granted hereunder will terminate
automatically upon any breach by You of the terms of this License.
Individuals or entities who have received Adaptations or Collections
from You under this License, however, will not have their licenses
terminated provided such individuals or entities remain in full
compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will
survive any termination of this License.
b. Subject to the above terms and conditions, the license granted here is
perpetual (for the duration of the applicable copyright in the Work).
Notwithstanding the above, Licensor reserves the right to release the
Work under different license terms or to stop distributing the Work at
any time; provided, however that any such election will not serve to
withdraw this License (or any other license that has been, or is
required to be, granted under the terms of this License), and this
License will continue in full force and effect unless terminated as
stated above.
8. Miscellaneous
a. Each time You Distribute or Publicly Perform the Work or a Collection,
the Licensor offers to the recipient a license to the Work on the same
terms and conditions as the license granted to You under this License.
b. Each time You Distribute or Publicly Perform an Adaptation, Licensor
offers to the recipient a license to the original Work on the same
terms and conditions as the license granted to You under this License.
c. If any provision of this License is invalid or unenforceable under
applicable law, it shall not affect the validity or enforceability of
the remainder of the terms of this License, and without further action
by the parties to this agreement, such provision shall be reformed to
the minimum extent necessary to make such provision valid and
enforceable.
d. No term or provision of this License shall be deemed waived and no
breach consented to unless such waiver or consent shall be in writing
and signed by the party to be charged with such waiver or consent.
e. This License constitutes the entire agreement between the parties with
respect to the Work licensed here. There are no understandings,
agreements or representations with respect to the Work not specified
here. Licensor shall not be bound by any additional provisions that
may appear in any communication from You. This License may not be
modified without the mutual written agreement of the Licensor and You.
f. The rights granted under, and the subject matter referenced, in this
License were drafted utilizing the terminology of the Berne Convention
for the Protection of Literary and Artistic Works (as amended on
September 28, 1979), the Rome Convention of 1961, the WIPO Copyright
Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996
and the Universal Copyright Convention (as revised on July 24, 1971).
These rights and subject matter take effect in the relevant
jurisdiction in which the License terms are sought to be enforced
according to the corresponding provisions of the implementation of
those treaty provisions in the applicable national law. If the
standard suite of rights granted under applicable copyright law
includes additional rights not granted under this License, such
additional rights are deemed to be included in the License; this
License is not intended to restrict the license of any rights under
applicable law.
Creative Commons Notice
Creative Commons is not a party to this License, and makes no warranty
whatsoever in connection with the Work. Creative Commons will not be
liable to You or any party on any legal theory for any damages
whatsoever, including without limitation any general, special,
incidental or consequential damages arising in connection to this
license. Notwithstanding the foregoing two (2) sentences, if Creative
Commons has expressly identified itself as the Licensor hereunder, it
shall have all rights and obligations of Licensor.
Except for the limited purpose of indicating to the public that the
Work is licensed under the CCPL, Creative Commons does not authorize
the use by either party of the trademark "Creative Commons" or any
related trademark or logo of Creative Commons without the prior
written consent of Creative Commons. Any permitted use will be in
compliance with Creative Commons' then-current trademark usage
guidelines, as may be published on its website or otherwise made
available upon request from time to time. For the avoidance of doubt,
this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.
===============================================================================
The Go Programming Language
https://golang.org/LICENSE
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===============================================================================
The Rust Programming Language
https://github.com/rust-lang/rust/blob/master/LICENSE-MIT
Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
===============================================================================
The Rust Programming Language
https://github.com/rust-lang/rust/blob/master/LICENSE-APACHE
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================================================
crossbeam-channel 0.5.17, crossbeam-deque 0.8.8, crossbeam-epoch 0.9.21, crossbeam-utils 0.8.23
================================================================================
The MIT License (MIT)
@@ -7716,6 +8314,30 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
keyboard-types 0.8.3
================================================================================
Copyright (c) 2017 Pyfisch
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================================================
keyring 3.6.3
================================================================================
@@ -9452,6 +10074,56 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
muda 0.21.2
================================================================================
MIT License
Copyright (c) 2022-2022 Tauri Programme within The Commons Conservancy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
muda 0.21.2
================================================================================
SPDXVersion: SPDX-2.1
DataLicense: CC0-1.0
PackageName: muda
DataFormat: SPDXRef-1
PackageSupplier: Organization: The Tauri Programme in the Commons Conservancy
PackageHomePage: https://tauri.app
PackageLicenseDeclared: Apache-2.0
PackageLicenseDeclared: MIT
PackageCopyrightText: 2020-2022, The Tauri Programme in the Commons Conservancy
PackageSummary: <text>Menu Utilities for Desktop Applications.
</text>
PackageComment: <text>The package includes the following libraries; see
Relationship information.
</text>
Created: 2022-12-05T09:00:00Z
PackageDownloadLocation: git://github.com/tauri-apps/muda
PackageDownloadLocation: git+https://github.com/tauri-apps/muda.git
PackageDownloadLocation: git+ssh://github.com/tauri-apps/muda.git
Creator: Person: Daniel Thompson-Yvetot
================================================================================
multiversion_no_op 1.0.0
================================================================================
@@ -11781,6 +12453,32 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
The files under third-party/chromium are licensed as described in
third-party/chromium/LICENSE.
================================================================================
ruzstd 0.9.1
================================================================================
MIT License
Copyright (c) 2019 Moritz Borcherding
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
schannel 0.1.29
================================================================================
@@ -13042,6 +13740,32 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
================================================================================
twox-hash 2.1.5
================================================================================
The MIT License (MIT)
Copyright (c) 2015 Jake Goulding
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
type-map 0.5.1
================================================================================
@@ -15663,7 +16387,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
================================================================================
zip 8.6.0, zip 9.0.4
zip 9.0.4
================================================================================
The MIT License (MIT)
@@ -15918,89 +16642,6 @@ Apache License
See the License for the specific language governing permissions and
limitations under the License.
================================================================================
zstd 0.13.3
================================================================================
The MIT License (MIT)
Copyright (c) 2016 Alexandre Bury
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================================================
zstd 0.14.2, zstd-safe 7.3.0, zstd-safe 8.1.0, zstd-sys 2.1.1+zstd.1.5.7
================================================================================
BSD 3-Clause License
Copyright (c) 2026, Alexandre Bury
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
zstd-sys 2.1.1+zstd.1.5.7
================================================================================
The auto-generated bindings are under the 3-clause BSD license:
BSD License
For Zstandard software
Copyright (c) Meta Platforms, Inc. and affiliates. All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name Facebook, nor Meta, nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
================================================================================
zune-core 0.5.3, zune-jpeg 0.5.15
================================================================================
@@ -16068,13 +16709,13 @@ accesskit_winit 0.32.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
array-macro 2.1.8: MIT OR Apache-2.0 (https://codeberg.org/xfix/array-macro)
block2 0.5.1: MIT (https://github.com/madsmtm/objc2)
block2 0.6.2: MIT (https://github.com/madsmtm/objc2)
cadmpeg-codec-freecad 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-rhino 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-step 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-container 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-core 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-ir 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-registry 0.6.1: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-freecad 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-rhino 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-codec-step 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-container 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-core 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-ir 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
cadmpeg-registry 0.6.3: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
clipboard-win 5.4.1: BSL-1.0 (https://github.com/DoumanAsh/clipboard-win)
convert_case 0.4.0: MIT (https://github.com/rutrum/convert-case)
dispatch 0.2.0: MIT (http://github.com/SSheldon/rust-dispatch)
+5
View File
@@ -19,6 +19,11 @@ path = "src/main.rs"
name = "solvecraft-cli"
path = "src/alias.rs"
[features]
default = ["raytrace-cpu"]
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
[dependencies]
solidcraft-engine = { workspace = true }
solidcraft-gpu = { workspace = true }
@@ -0,0 +1,151 @@
//! Reproducible evidence through the production command/job interfaces.
//! Usage: studio_evidence RECIPE.json RENDER-PARAMS.json OUTPUT.png
//! Writes the editable design, command timings and actual adapter/job metadata beside PNG.
//! Accepts runtime requests as well as inline or named saved render jobs.
use serde_json::{Value, json};
use solidcraft_engine::{
Session,
doc::render_settings::{RenderJob, RenderSetup},
render::job::{CpuHost, ErrorCode, JobId, Phase},
};
use std::{
io::Read,
path::Path,
time::{Duration, Instant},
};
fn read_json(path: &str) -> Result<Value, String> {
let mut bytes = Vec::new();
std::fs::File::open(path).map_err(|e| format!("{path}: {e}"))?.take(16_777_217).read_to_end(&mut bytes).map_err(|e| e.to_string())?;
if bytes.len() > 16_777_216 {
return Err("evidence input exceeds 16 MiB".into());
}
serde_json::from_slice(&bytes).map_err(|e| format!("{path}: {e}"))
}
// Only decide which optional host must be installed. The production command
// still validates and resolves the actual request without a second wire format.
fn needs_gpu(session: &Session, params: &Value) -> Result<bool, String> {
let inline = params.get("settings").filter(|value| !value.is_null());
let named = params.get("setup").filter(|value| !value.is_null());
if inline.is_some() && named.is_some() {
return Err("choose either setup or settings".into());
}
let inline_setup = inline
.filter(|value| value.get("job").is_some_and(|job| !job.is_null()))
.map(|value| serde_json::from_value::<RenderSetup>(value.clone()).map_err(|e| e.to_string()))
.transpose()?;
let setup = if let Some(name) = named.and_then(Value::as_str) {
Some(session.doc.render.setups.get(name).ok_or("no saved render setup with that name")?)
} else {
inline_setup.as_ref()
};
if let Some(setup) = setup
&& let Some(job) = &setup.job
{
if params.as_object().is_some_and(|object| object.keys().any(|key| !matches!(key.as_str(), "setup" | "settings" | "replace"))) {
return Err("saved render jobs cannot be mixed with runtime controls".into());
}
setup.validate().map_err(|e| e.to_string())?;
return Ok(matches!(job, RenderJob::GpuPath { .. }));
}
Ok(params.get("progressive").and_then(Value::as_bool) == Some(true))
}
fn run() -> Result<(), String> {
let args: Vec<String> = std::env::args().skip(1).collect();
let [recipe_path, params_path, output_path] = args.as_slice() else {
return Err("usage: studio_evidence RECIPE.json RENDER-PARAMS.json OUTPUT.png".into());
};
let output = Path::new(output_path);
if !output.extension().is_some_and(|e| e.eq_ignore_ascii_case("png")) {
return Err("output must have a .png extension".into());
}
let recipe = read_json(recipe_path)?;
let params = read_json(params_path)?;
let mut session = Session::default();
eprintln!("Generating original CAD scene from {recipe_path}");
let generation_start = Instant::now();
let (commands, command_timings) = session.run_script_timed(&recipe, false).map_err(|e| e.to_string())?;
let generation_seconds = generation_start.elapsed().as_secs_f64();
let design_path = output.with_extension("solidcraft");
session.execute("file.save_as", &json!({"path":design_path})).map_err(|e| e.to_string())?;
let scene = solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?;
let bodies: Vec<Value> = scene
.meshes
.iter()
.map(|mesh| json!({"name":mesh.name,"vertices":mesh.mesh.positions.len(),"triangles":mesh.mesh.triangles.len()}))
.collect();
drop(scene);
let cpu_host_start = Instant::now();
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
let cpu_host_seconds = cpu_host_start.elapsed().as_secs_f64();
// Match runtime and saved-job dispatch. CPU evidence must not require a
// Vulkan device, parse GPU policy or initialize a GPU host.
let (adapter_report, gpu_host_seconds) = if needs_gpu(&session, &params)? {
let gpu_host_start = Instant::now();
let policy = solidcraft_gpu::select::Policy::from_env().map_err(|e| e.to_string())?;
let (adapter, report) =
solidcraft_gpu::select::pick_vulkan(policy, |_| true).ok_or("no Vulkan adapter satisfies the requested GPU policy")?;
session.register_render_job_host(solidcraft_gpu::path::host_on_adapter(&adapter).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
(Some(report), Some(gpu_host_start.elapsed().as_secs_f64()))
} else {
(None, None)
};
let preflight = session.execute("render.preflight", &params).map_err(|e| e.to_string())?;
let start = Instant::now();
let started = session.execute("render.start", &params).map_err(|e| e.to_string())?;
let executor = started
.get("resolved")
.and_then(|resolved| resolved.get("executor"))
.and_then(Value::as_str)
.ok_or("started job did not report the resolved image executor")?
.to_owned();
let id: JobId = serde_json::from_value(started.get("job").cloned().ok_or("missing job id")?).map_err(|e| e.to_string())?;
let handle = session.render_job_handle(id).map_err(|e| e.to_string())?;
eprintln!("Rendering through production {executor} executor");
if let Some(adapter) = &adapter_report {
eprintln!("GPU adapter: {adapter:?}");
}
let status = loop {
match handle.wait_timeout(Duration::from_secs(10)) {
Ok(status) => break status,
Err(error) if error.code == ErrorCode::TimedOut && start.elapsed() < Duration::from_secs(3600) => {
let status = handle.status().map_err(|e| e.to_string())?;
eprintln!(
"Render progress: {:?} {}/{} after {:.1}s",
status.progress.phase,
status.progress.done,
status.progress.total,
start.elapsed().as_secs_f64()
);
}
Err(error) => {
let _ = handle.cancel();
return Err(error.to_string());
}
}
};
let render_and_encode_seconds = start.elapsed().as_secs_f64();
if status.progress.phase != Phase::Completed {
return Err(status.error.map_or_else(|| "render did not complete".into(), |e| e.to_string()));
}
let exported = session.execute("render.export", &json!({"job":id,"path":output_path,"overwrite":true})).map_err(|e| e.to_string())?;
let report = json!({
"recipe":recipe_path,"render_params":params,"executor":executor,"adapter":adapter_report,
"environment":{"SOLIDCRAFT_GPU":std::env::var("SOLIDCRAFT_GPU").ok(),"WGPU_BACKEND":std::env::var("WGPU_BACKEND").ok()},
"cad_generation_seconds":generation_seconds,"cpu_host_setup_seconds":cpu_host_seconds,"gpu_host_setup_seconds":gpu_host_seconds,
"render_and_encode_seconds":render_and_encode_seconds,
"timing_scope":"wall time from render.start through completed PNG encoding; includes job preparation and readback, excludes CAD generation, preflight and initial CPU/GPU host setup; GPU setup timing includes adapter selection and pipelines when requested",
"bodies":bodies,"commands":commands,"command_timings":command_timings,
"preflight":preflight,"export":exported,"editable_design":design_path
});
std::fs::write(output.with_extension("json"), serde_json::to_vec_pretty(&report).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
println!("{}", serde_json::to_string_pretty(&report).map_err(|e| e.to_string())?);
Ok(())
}
fn main() -> Result<(), String> {
run()
}
+2 -1
View File
@@ -36,8 +36,9 @@ impl Script {
);
}
/// A part placed after it is built: free, even as the design's first part (#2804).
fn component(&mut self, name: &str) {
self.c("component.create", json!({"name": name}));
self.c("component.create", json!({"name": name, "grounded": false}));
}
fn root(&mut self) {
+701
View File
@@ -0,0 +1,701 @@
//! `bench-assistant`: the standard Assistant model test (v1, #4851), run against a desktop app.
//!
//! The app is already running with `--control PORT`; everything goes through its control channel
//! (`docs/control-protocol.md`), so the command needs no window code of its own. For each task it
//! starts a new design, runs the task's prescribed setup (a box, for some), adds the endpoint as an
//! API agent with All permissions, starts a new chat, sends the prompt once and waits for the turn
//! to end. The result is checked on the design (`document.inspect` with measurements), never on
//! what the model says, except for the one question whose answer is the reply. A failed task is
//! put in one of the issue's failure categories from the conversation's items.
//!
//! ```text
//! solidcraft-cli bench-assistant --connect 127.0.0.1:PORT --base-url URL --model NAME
//! [--token T] [--tasks 1,2,5] [--repeat N] [--timeout-s 300]
//! [--json out.json] [--markdown out.md]
//! ```
//! prints a table of the results; `--markdown` writes the issue's comment template filled in,
//! `--json` every task's rules, conversation and design before and after.
use std::io::{BufRead, BufReader, Write};
use std::net::{SocketAddr, TcpStream};
use std::time::{Duration, Instant};
use serde_json::{Value, json};
/// Standard test v1, as #4851 states it. Changing a prompt or a rule makes it another test.
const TASKS: &str = r#"[
{"id": "01-cube", "prompt": "Make a 20 mm cube.", "check": {"bodies": 1, "volume": 8000, "bbox_size": [20, 20, 20]}},
{"id": "02-cylinder", "prompt": "Make a cylinder 30 mm in diameter and 50 mm tall.", "check": {"bodies": 1, "volume": 35342.92, "bbox_size": [30, 30, 50]}},
{"id": "03-block", "prompt": "Create a block 60 mm long, 40 mm wide and 10 mm thick.", "check": {"bodies": 1, "volume": 24000, "bbox_size": [10, 40, 60]}},
{"id": "04-sketch-extrude", "prompt": "Sketch a 50 by 30 mm rectangle on the XY plane and extrude it 15 mm.", "check": {"bodies": 1, "volume": 22500, "bbox_size": [15, 30, 50], "feature_kinds": ["Extrude"]}},
{"id": "05-plate-hole", "prompt": "Make a plate 80 x 60 x 10 mm with a 20 mm diameter hole straight through the middle.", "check": {"bodies": 1, "volume": 44858.41, "bbox_size": [10, 60, 80], "note": "80*60*10 - pi*10^2*10"}},
{"id": "06-fillet-box", "prompt": "Round all the edges of the box with a 3 mm radius.", "setup": [{"command": "solid.box", "params": {"length": 40, "width": 40, "height": 40}}], "check": {"bodies": 1, "volume": 63108.53, "tol": 0.003, "feature_kinds": ["Fillet"], "note": "all 12 edges of a 40 mm cube rounded r=3"}},
{"id": "07-move-body", "prompt": "Move the box 50 mm along the X axis.", "setup": [{"command": "solid.box", "params": {"length": 20, "width": 20, "height": 20}}], "check": {"bodies": 1, "volume": 8000, "bbox_min": [50, 0, 0]}},
{"id": "08-rename-body", "prompt": "Rename the body to Bracket.", "setup": [{"command": "solid.box", "params": {"length": 30, "width": 20, "height": 10}}], "check": {"bodies": 1, "volume": 6000, "body_name": "Bracket"}},
{"id": "09-volume-question", "prompt": "What is the volume of the part?", "setup": [{"command": "solid.box", "params": {"length": 30, "width": 20, "height": 10}}], "check": {"bodies": 1, "volume": 6000, "unchanged": true, "reply_number": "6[ ,. ]?000(?![0-9])|6(\\.0+)? ?cm"}},
{"id": "10-two-holes", "prompt": "Make a 100 x 50 x 5 mm plate and drill two 10 mm holes through it, each 15 mm in from the short ends and centred across the width.", "check": {"bodies": 1, "volume": 24214.6, "bbox_size": [5, 50, 100], "note": "100*50*5 - 2*pi*5^2*5"}},
{"id": "11-any-change", "prompt": "can you use the software to make a change to the current shape, any change at all", "setup": [{"command": "solid.box", "params": {"length": 30, "width": 20, "height": 10}}], "check": {"changed": true, "note": "verbatim user prompt from #4846; pass = the design changed"}}
]"#;
/// The agent the test adds. A fixed id replaces it on every run instead of adding another.
const AGENT_ID: &str = "api-bench-assistant";
const AGENT_NAME: &str = "bench";
/// Longest control reply read (the channel's own request limit).
const MAX_REPLY: usize = 16 << 20;
/// Most result data kept for `--json`.
const MAX_RESULTS: usize = 64 << 20;
/// Deadline of the requests that only set up or read (each is answered at once by the app).
const REQUEST_TIMEOUT: Duration = Duration::from_secs(120);
/// How often a running turn is polled.
const POLL: Duration = Duration::from_millis(250);
/// After this long, an idle chat with no reply yet counts as a turn that ended without one.
const START_GRACE: Duration = Duration::from_secs(30);
/// Absolute tolerance of the lowest corner, in millimetres.
const MIN_CORNER_TOL: f64 = 0.05;
/// A JSON-lines connection to the app. Requests are sent once; a lost reply is never retried,
/// so nothing runs twice.
struct Control {
addr: SocketAddr,
token: String,
reader: BufReader<TcpStream>,
id: u64,
}
impl Control {
fn connect(addr: &str, token: Option<&str>) -> Result<Self, String> {
let addr: SocketAddr = addr.parse().map_err(|_| format!("--connect: `{addr}` is not an IP address and port"))?;
if !addr.ip().is_loopback() || addr.port() == 0 {
return Err("--connect requires a loopback address and a nonzero port (the app listens on 127.0.0.1)".into());
}
let token = solidcraft_engine::control_auth::client_token(addr.port(), token)
.map(|t| t.token)
.ok_or("no control token: pass --token, set SOLIDCRAFT_CONTROL_TOKEN, or run on the app's computer (its token file)")?;
if token.len() > 4096 {
return Err("the control token is longer than 4096 bytes".into());
}
Self::open(addr, token)
}
fn open(addr: SocketAddr, token: String) -> Result<Self, String> {
let stream = TcpStream::connect_timeout(&addr, Duration::from_secs(5)).map_err(|e| format!("connecting to {addr}: {e}"))?;
Ok(Self { addr, token, reader: BufReader::new(stream), id: 0 })
}
/// A new connection to the same app: a request that outlived its deadline keeps the old one
/// busy until it is answered.
fn reopen(&self) -> Result<Self, String> {
Self::open(self.addr, self.token.clone())
}
fn call(&mut self, method: &str, params: Value, deadline: Instant) -> Result<Value, String> {
let left = || deadline.checked_duration_since(Instant::now()).filter(|d| !d.is_zero()).ok_or_else(|| "timeout".to_string());
let io = |e: std::io::Error| match e.kind() {
std::io::ErrorKind::TimedOut | std::io::ErrorKind::WouldBlock => "timeout".to_string(),
_ => format!("control channel: {e}"),
};
self.reader.get_ref().set_write_timeout(Some(left()?)).map_err(io)?;
self.id += 1;
// The first request on a connection carries the token; sending it on every one is allowed.
let request = json!({"id": self.id, "method": method, "params": params, "token": self.token});
writeln!(self.reader.get_mut(), "{request}").map_err(io)?;
let mut line = Vec::new();
loop {
self.reader.get_ref().set_read_timeout(Some(left()?)).map_err(io)?;
let bytes = self.reader.fill_buf().map_err(io)?;
if bytes.is_empty() {
return Err("the app closed the control connection".into());
}
let newline = bytes.iter().position(|b| *b == b'\n');
let used = newline.map_or(bytes.len(), |n| n + 1);
if line.len() + used > MAX_REPLY {
return Err("a control reply is larger than 16 MiB".into());
}
line.extend_from_slice(bytes.get(..used).unwrap_or_default());
self.reader.consume(used);
if newline.is_some() {
break;
}
}
let reply: Value = serde_json::from_slice(&line).map_err(|e| format!("control reply: {e}"))?;
if reply["id"].as_u64() != Some(self.id) {
return Err("control reply for another request".into());
}
if reply["ok"] != true {
return Err(format!("{method}: {}", reply["error"].as_str().unwrap_or("failed")));
}
reply.get("result").cloned().ok_or_else(|| format!("{method}: reply without a result"))
}
fn execute(&mut self, command: &str, params: Value) -> Result<Value, String> {
self.call("engine.execute", json!({"command": command, "params": params, "animate": false}), Instant::now() + REQUEST_TIMEOUT)
}
fn chat(&mut self, params: Value) -> Result<Value, String> {
self.call("ui.agent_chat", params, Instant::now() + REQUEST_TIMEOUT)
}
fn inspect(&mut self) -> Result<Value, String> {
let design = self.call("document.inspect", json!({"measure": true}), Instant::now() + REQUEST_TIMEOUT)?;
if design.is_object() { Ok(design) } else { Err("document.inspect returned no design".into()) }
}
}
fn tasks() -> Result<Vec<Value>, String> {
serde_json::from_str(TASKS).map_err(|e| format!("embedded tasks: {e}"))
}
fn triple(value: &Value) -> Option<[f64; 3]> {
match value.as_array()?.as_slice() {
[x, y, z] => {
let n = |v: &Value| v.as_f64().filter(|n| n.is_finite());
Some([n(x)?, n(y)?, n(z)?])
}
_ => None,
}
}
/// What the model said (thoughts left out).
fn reply(items: &[Value]) -> String {
items.iter().filter(|i| i["thought"] != true).filter_map(|i| i["agent"].as_str()).collect::<Vec<_>>().join("\n")
}
/// v1's reply pattern `6[ ,. ]?000(?![0-9])|6(\.0+)? ?cm`: "6000", "6 000", "6,000", "6.000" not
/// followed by a digit, or "6 cm", "6.0cm", … (a narrow no-break space also separates thousands).
fn volume_reply(text: &str) -> bool {
text.match_indices('6').any(|(at, _)| {
let tail = text.get(at + 1..).unwrap_or_default();
let thousands = |t: &str| t.strip_prefix("000").is_some_and(|rest| !rest.starts_with(|c: char| c.is_ascii_digit()));
let separated = tail.strip_prefix([' ', ',', '.', '\u{202f}']).is_some_and(thousands);
let cm = {
let t = tail.strip_prefix(".0").map_or(tail, |t| t.trim_start_matches('0'));
t.strip_prefix(' ').unwrap_or(t).starts_with("cm")
};
thousands(tail) || separated || cm
})
}
/// The design as the user sees it, without what reading or saving changes (path, saved state,
/// revision, selection, undo depth, evaluation timings).
fn content(design: &Value) -> Value {
let mut v = design.clone();
if let Some(o) = v.as_object_mut() {
for key in ["path", "dirty", "revision", "selection", "undo", "redo"] {
o.remove(key);
}
}
for feature in v.get_mut("timeline").and_then(Value::as_array_mut).into_iter().flatten() {
if let Some(o) = feature.as_object_mut() {
o.remove("ms");
o.remove("boolean_diagnostics");
}
}
v
}
/// Whether the design changed: its content, or a step on the undo stack (a colour, say, that the
/// inspection does not list).
fn changed(before: &Value, after: &Value) -> bool {
content(before) != content(after) || after["undo"].as_u64() > before["undo"].as_u64()
}
/// The rules a task's result breaks (none: it passed). `before` and `after` are
/// `document.inspect {measure: true}` before the prompt and after the turn.
pub(super) fn check(rule: &Value, before: &Value, after: &Value, items: &[Value]) -> Vec<String> {
let Some(all) = after["bodies"].as_array() else { return vec!["the design could not be read".into()] };
// Bodies the user sees; a hidden one (a tool body, a hidden original) is not the result.
let bodies: Vec<&Value> = all.iter().filter(|b| b["visible"] != false).collect();
let mut errors = Vec::new();
if let Some(n) = rule["bodies"].as_u64()
&& bodies.len() as u64 != n
{
errors.push(format!("{} bodies, expected {n}", bodies.len()));
}
if let Some(expected) = rule["volume"].as_f64() {
let tol = rule["tol"].as_f64().unwrap_or(0.01);
let volumes: Vec<f64> = bodies.iter().filter_map(|b| b["volume_mm3"].as_f64()).filter(|v| v.is_finite()).collect();
let total: f64 = volumes.iter().sum();
if volumes.len() != bodies.len() || (total - expected).abs() > expected * tol {
errors.push(format!("volume {total:.1} mm³, expected {expected} mm³ ±{}%", tol * 100.0));
}
}
let first = bodies.first().copied().unwrap_or(&Value::Null);
let min = triple(&first["bbox"]["min"]);
if let Some(mut expected) = triple(&rule["bbox_size"]) {
expected.sort_by(f64::total_cmp);
let size = min.zip(triple(&first["bbox"]["max"])).map(|(lo, hi)| {
let mut size = [hi[0] - lo[0], hi[1] - lo[1], hi[2] - lo[2]];
size.sort_by(f64::total_cmp);
size
});
if !size.is_some_and(|s| s.iter().zip(expected).all(|(a, e)| (a - e).abs() <= 0.01 * e.max(1.0))) {
errors.push(format!("size {size:?}, expected {expected:?} in any orientation"));
}
}
if let Some(expected) = triple(&rule["bbox_min"])
&& !min.is_some_and(|m| m.iter().zip(expected).all(|(a, e)| (a - e).abs() <= MIN_CORNER_TOL))
{
errors.push(format!("lowest corner {min:?}, expected {expected:?}"));
}
if let Some(name) = rule["body_name"].as_str()
&& !bodies.iter().any(|b| b["name"] == name)
{
errors.push(format!("no body named {name}"));
}
for kind in rule["feature_kinds"].as_array().into_iter().flatten().filter_map(Value::as_str) {
let live = |f: &&Value| f["suppressed"] != true && f["rolled_back"] != true && f["error"].is_null();
if !after["timeline"].as_array().into_iter().flatten().filter(live).any(|f| f["type"].as_str().is_some_and(|t| t.contains(kind))) {
errors.push(format!("no {kind} in the timeline"));
}
}
if rule["changed"] == true || rule["unchanged"] == true {
let changed = before.is_object() && changed(before, after);
if rule["changed"] == true && !changed {
errors.push("the design did not change".into());
}
if rule["unchanged"] == true && (changed || !before.is_object()) {
errors.push("the design changed".into());
}
}
if rule["reply_number"].is_string() && !volume_reply(&reply(items)) {
errors.push("the reply does not give the volume".into());
}
errors
}
/// A tool call written into the reply as JSON: `{"name": …`, `{"command": …`, `{"function": …`,
/// `{"tool": …`.
fn call_as_text(text: &str) -> bool {
text.match_indices('{').any(|(at, _)| {
let rest = text.get(at + 1..).unwrap_or_default().trim_start();
["\"name\"", "\"command\"", "\"function\"", "\"tool\""]
.iter()
.any(|key| rest.strip_prefix(key).is_some_and(|r| r.trim_start().starts_with(':')))
})
}
/// How a failed task failed, in the issue's words, from the conversation's items.
pub(super) fn classify(items: &[Value], timed_out: bool) -> &'static str {
if timed_out {
return "timeout";
}
let tools: Vec<&Value> = items.iter().filter(|i| i.get("tool").is_some()).collect();
// A failed call, or a batch whose answer reports a failed step (`"ok": false`).
let failed: Vec<String> = tools
.iter()
.map(|t| t["output"].as_str().unwrap_or_default())
.zip(&tools)
.filter(|(output, t)| t["status"] == "Failed" || serde_json::from_str::<Value>(output).is_ok_and(|v| v["ok"] == false))
.map(|(output, _)| output.to_lowercase())
.collect();
let notices: Vec<String> = items.iter().filter_map(|i| i["notice"].as_str()).map(str::to_lowercase).collect();
let answer = reply(items);
if notices.iter().any(|n| n.contains("http 400")) {
return "the endpoint refused tools (HTTP 400)";
}
if notices.iter().any(|n| n.contains("max_turn_requests")) || answer.contains("requests for one message") {
return "hit the request limit";
}
if call_as_text(&answer) {
return "wrote the call as text";
}
if failed.iter().any(|o| o.contains("unknown command") || o.starts_with("unknown tool")) {
return "wrong or unknown command";
}
if failed.iter().any(|o| o.contains("invalid parameters") || o.starts_with("not run:") || o.contains("not valid json")) {
return "wrong arguments";
}
if !failed.is_empty() || notices.iter().any(|n| n.starts_with("http ")) || items.iter().any(|i| i["error"] == true) {
return "gave up after errors";
}
if tools.is_empty() {
return if answer.trim().is_empty() { "an empty reply" } else { "only talked" };
}
"wrong result"
}
/// One run's results in the issue's comment template (`run`: which repetition).
pub(super) fn markdown(model: &str, version: &str, results: &[Value], run: u64) -> String {
let clean = |s: &str| s.replace(['\n', '\r', '|'], " ");
let rows: Vec<&Value> = results.iter().filter(|r| r["repeat"] == run).collect();
let passed = rows.iter().filter(|r| r["passed"] == true).count();
let mut out = format!(
"### {} (<quant>, <download size>)\nRuntime: <Ollama/LM Studio/... version>, context <num_ctx if changed>\nSolidCraft: {}\nResult: {passed}/11\n| # | pass/fail | how it failed |\n|---|---|---|\n",
clean(model),
clean(version)
);
for n in 1..=11 {
match rows.iter().find(|r| r["task"] == n) {
Some(r) if r["passed"] == true => out.push_str(&format!("| {n} | pass | |\n")),
Some(r) => out.push_str(&format!("| {n} | fail | {} |\n", clean(r["failure"].as_str().unwrap_or_default()))),
None => out.push_str(&format!("| {n} | | not run |\n")),
}
}
out.push_str(if rows.len() == 11 {
"Notes: <anything odd, e.g. very slow, needed a retry>\n"
} else {
"Notes: partial run; the tasks marked \"not run\" were left out.\n"
});
out
}
fn write(path: &str, text: &str) -> Result<(), String> {
solidcraft_engine::io::vfs::write(path, text.as_bytes()).map_err(|e| format!("{path}: {e}"))
}
struct Options<'a> {
connect: &'a str,
token: Option<&'a str>,
base_url: &'a str,
model: &'a str,
tasks: Vec<usize>,
repeat: u64,
timeout: Duration,
json: Option<&'a str>,
markdown: Option<&'a str>,
}
fn options(args: &[String]) -> Result<Options<'_>, String> {
const FLAGS: [&str; 9] = ["--connect", "--token", "--base-url", "--model", "--tasks", "--repeat", "--timeout-s", "--json", "--markdown"];
let mut given = std::collections::BTreeMap::new();
let mut args = args.iter();
while let Some(flag) = args.next() {
let Some(flag) = FLAGS.iter().find(|f| **f == flag.as_str()) else { return Err(format!("unknown bench-assistant option: {flag}")) };
let value = args
.next()
.filter(|v| !v.starts_with("--") && !v.is_empty() && v.len() <= 4096)
.ok_or_else(|| format!("{flag} needs a value (at most 4096 bytes)"))?;
if given.insert(*flag, value.as_str()).is_some() {
return Err(format!("{flag} given twice"));
}
}
let required = |key: &str| given.get(key).copied().ok_or_else(|| format!("bench-assistant needs {key}"));
let number = |key: &str, default: u64, max: u64| -> Result<u64, String> {
given
.get(key)
.map_or(Ok(default), |s| s.parse::<u64>().ok().filter(|n| (1..=max).contains(n)).ok_or_else(|| format!("{key} must be 1 to {max}")))
};
let tasks = match given.get("--tasks") {
None => (1..=11).collect(),
Some(list) => {
let mut tasks = Vec::new();
for n in list.split(',') {
let n = n
.trim()
.parse::<usize>()
.ok()
.filter(|n| (1..=11).contains(n))
.ok_or("--tasks takes task numbers 1 to 11, separated by commas")?;
if tasks.contains(&n) {
return Err(format!("--tasks lists task {n} twice"));
}
tasks.push(n);
}
tasks
}
};
Ok(Options {
connect: required("--connect")?,
token: given.get("--token").copied(),
base_url: required("--base-url")?,
model: required("--model")?,
tasks,
repeat: number("--repeat", 1, 100)?,
timeout: Duration::from_secs(number("--timeout-s", 300, 3600)?),
json: given.get("--json").copied(),
markdown: given.get("--markdown").copied(),
})
}
/// Waits for the turn the prompt started to end. Returns the chat's last state and whether the
/// deadline passed first.
fn wait_for_turn(control: &mut Control, mut chat: Value, began: Instant, end: Instant) -> Result<(Value, bool), String> {
let mut seen_busy = false;
loop {
let state = chat["state"].as_str().unwrap_or_default();
let turn = !chat["turn"].is_null();
let queued = chat["pending"].as_array().is_some_and(|p| !p.is_empty());
seen_busy |= state == "busy" || turn;
let idle = !matches!(state, "busy" | "starting") && !turn && !queued;
// A turn can start and end between two polls: then the conversation has its answer.
let answered = chat["items"].as_array().into_iter().flatten().any(|i| i.get("user").is_none());
if idle && (seen_busy || answered || matches!(state, "exited" | "auth" | "off") || began.elapsed() >= START_GRACE) {
return Ok((chat, false));
}
let Some(left) = end.checked_duration_since(Instant::now()).filter(|d| !d.is_zero()) else { return Ok((chat, true)) };
std::thread::sleep(POLL.min(left));
match control.call("ui.agent_chat", json!({}), end) {
Ok(next) => chat = next,
Err(e) if e == "timeout" => return Ok((chat, true)),
Err(e) => return Err(e),
}
}
}
pub fn run(args: &[String]) -> Result<(), String> {
let o = options(args)?;
let tasks = tasks()?;
let mut control = Control::connect(o.connect, o.token)?;
let help = control.call("ui.help", json!({}), Instant::now() + REQUEST_TIMEOUT)?;
let version = help["version"].as_str().filter(|s| !s.is_empty() && s.len() <= 256).ok_or("the app reported no version")?.to_string();
let config = control.chat(json!({
"features": true,
"add_agent": {"id": AGENT_ID, "name": AGENT_NAME, "kind": "api", "base_url": o.base_url, "model": o.model},
}))?;
if !config["agents"].as_array().into_iter().flatten().any(|a| a["id"] == AGENT_ID) {
return Err("the app did not add the benchmark's agent".into());
}
// Put the user's permission setting back afterwards.
let permissions = config["permissions"].as_str().map(str::to_string);
let result = run_tasks(&mut control, &o, &tasks, &version);
let _ = control.chat(json!({"stop": true}));
if let Some(p) = permissions {
let _ = control.chat(json!({"permissions": p}));
}
result
}
fn run_tasks(control: &mut Control, o: &Options, tasks: &[Value], version: &str) -> Result<(), String> {
let mut results: Vec<Value> = Vec::new();
let mut kept = 0;
let mut stdout = std::io::stdout().lock();
let mut say = |line: String| writeln!(stdout, "{line}").map_err(|e| format!("stdout: {e}"));
say(format!("bench-assistant v1: {} on SolidCraft {version}", o.model))?;
say("| run | # | task | pass/fail | how it failed | seconds |\n|---|---|---|---|---|---|".into())?;
for run in 1..=o.repeat {
for &n in &o.tasks {
let task = tasks.get(n - 1).ok_or("missing embedded task")?;
// A fresh design and a fresh chat, with nothing of the last task still running.
control.chat(json!({"stop": true}))?;
control.execute("file.new", json!({"name": "Benchmark"}))?;
for step in task["setup"].as_array().into_iter().flatten() {
control.execute(step["command"].as_str().ok_or("embedded setup without a command")?, step["params"].clone())?;
}
control.chat(json!({"agent": AGENT_ID, "permissions": "all", "new_chat": true, "attach_view": false}))?;
let before = control.inspect()?;
let began = Instant::now();
let end = began + o.timeout;
let (chat, timed_out) = match control.call("ui.agent_chat", json!({"send": task["prompt"]}), end) {
Ok(chat) => wait_for_turn(control, chat, began, end)?,
Err(e) if e == "timeout" => (json!({}), true),
Err(e) => return Err(e),
};
if timed_out {
// The late reply would arrive on the old connection; stop the model on a new one
// before reading the design.
*control = control.reopen()?;
}
// Stop the agent before reading the result, so nothing changes the design meanwhile.
let stopped = control.chat(json!({"stop": true}))?;
let items = stopped["items"].as_array().or(chat["items"].as_array()).cloned().unwrap_or_default();
let after = control.inspect()?;
let failed_rules = check(&task["check"], &before, &after, &items);
let passed = failed_rules.is_empty();
let failure = if passed { "" } else { classify(&items, timed_out) };
let seconds = began.elapsed().as_secs_f64();
say(format!(
"| {run} | {n} | {} | {} | {failure} | {seconds:.0} |",
task["id"].as_str().unwrap_or_default(),
if passed { "pass" } else { "fail" }
))?;
let result = json!({
"task": n, "id": task["id"], "prompt": task["prompt"], "repeat": run, "passed": passed, "failure": failure,
"failed_rules": failed_rules, "timed_out": timed_out, "seconds": seconds, "check": task["check"],
"items": items, "before": before, "after": after,
});
kept += result.to_string().len();
if kept > MAX_RESULTS {
return Err("the results passed 64 MiB; run fewer tasks or repetitions".into());
}
results.push(result);
// Written after every task, so an interrupted run keeps what it measured.
if let Some(path) = o.json {
let all = json!({"test": "v1", "model": o.model, "base_url": o.base_url, "solidcraft": version, "results": results});
write(path, &serde_json::to_string_pretty(&all).map_err(|e| e.to_string())?)?;
}
if let Some(path) = o.markdown {
write(path, &(1..=run).map(|r| markdown(o.model, version, &results, r)).collect::<Vec<_>>().join("\n"))?;
}
}
}
for run in 1..=o.repeat {
let rows: Vec<&Value> = results.iter().filter(|r| r["repeat"] == run).collect();
say(format!("run {run}: {}/{} passed", rows.iter().filter(|r| r["passed"] == true).count(), rows.len()))?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
fn design(bodies: Value, timeline: Value) -> Value {
json!({"name": "Benchmark", "params": [], "timeline": timeline, "bodies": bodies, "sketches": [], "undo": 1, "revision": 3, "dirty": true})
}
fn body(name: &str, volume: f64, min: [f64; 3], max: [f64; 3]) -> Value {
json!({"name": name, "volume_mm3": volume, "bbox": {"min": min, "max": max}, "visible": true})
}
#[test]
fn the_embedded_tasks_are_v1() {
let tasks = tasks().unwrap();
assert_eq!(tasks.len(), 11);
assert_eq!(tasks[0]["prompt"], "Make a 20 mm cube.");
assert_eq!(tasks[10]["prompt"], "can you use the software to make a change to the current shape, any change at all");
assert_eq!(tasks[5]["check"]["tol"], 0.003);
assert_eq!(tasks[6]["check"]["bbox_min"], json!([50, 0, 0]));
let boxes: Vec<_> = tasks.iter().filter(|t| t["setup"].is_array()).map(|t| t["id"].as_str().unwrap()).collect();
assert_eq!(boxes, ["06-fillet-box", "07-move-body", "08-rename-body", "09-volume-question", "11-any-change"]);
}
#[test]
fn pass_rules() {
let tasks = tasks().unwrap();
let rule = |n: usize| tasks[n - 1]["check"].clone();
let empty = design(json!([]), json!([]));
let pass = |n: usize, before: &Value, after: &Value, items: &[Value]| check(&rule(n), before, after, items);
// 3: any orientation, within 1%.
let block = design(json!([body("Body1", 24_100.0, [0.0; 3], [40.0, 10.0, 60.2])]), json!([{"type": "Box"}]));
assert!(pass(3, &empty, &block, &[]).is_empty());
let thick = design(json!([body("Body1", 24_000.0, [0.0; 3], [40.0, 12.0, 60.0])]), json!([]));
assert_eq!(pass(3, &empty, &thick, &[]).len(), 1);
let two = design(json!([body("A", 12_000.0, [0.0; 3], [60.0, 40.0, 5.0]), body("B", 12_000.0, [0.0; 3], [60.0, 40.0, 5.0])]), json!([]));
assert!(!pass(3, &empty, &two, &[]).is_empty());
// A hidden body is not part of the result.
let mut hidden = two.clone();
hidden["bodies"][1]["visible"] = json!(false);
hidden["bodies"][0]["volume_mm3"] = json!(24_000.0);
hidden["bodies"][0]["bbox"]["max"] = json!([60.0, 40.0, 10.0]);
assert!(pass(3, &empty, &hidden, &[]).is_empty());
// 4: the timeline needs a working Extrude.
let plate = |kind: Value| design(json!([body("Body1", 22_500.0, [0.0; 3], [50.0, 30.0, 15.0])]), kind);
assert!(pass(4, &empty, &plate(json!([{"type": "Sketch"}, {"type": "Extrude"}])), &[]).is_empty());
assert!(!pass(4, &empty, &plate(json!([{"type": "Box"}])), &[]).is_empty());
for flag in [json!({"suppressed": true}), json!({"rolled_back": true}), json!({"error": "failed"})] {
let mut feature = json!({"type": "Extrude"});
feature.as_object_mut().unwrap().extend(flag.as_object().unwrap().clone());
assert!(!pass(4, &empty, &plate(json!([feature])), &[]).is_empty(), "{flag}");
}
// 6: the fillet's tolerance is 0.3%.
let start = design(json!([body("Body1", 64_000.0, [0.0; 3], [40.0; 3])]), json!([{"type": "Box"}]));
let filleted = |v: f64| design(json!([body("Body1", v, [0.0; 3], [40.0; 3])]), json!([{"type": "Box"}, {"type": "Fillet"}]));
assert!(pass(6, &start, &filleted(63_108.53 * 1.002), &[]).is_empty());
assert!(!pass(6, &start, &filleted(63_108.53 * 1.004), &[]).is_empty());
// 7: the lowest corner.
let moved = |x: f64| design(json!([body("Body1", 8000.0, [x, 0.0, 0.0], [x + 20.0, 20.0, 20.0])]), json!([]));
assert!(pass(7, &empty, &moved(50.0), &[]).is_empty());
assert!(!pass(7, &empty, &moved(0.0), &[]).is_empty());
// 8: the name.
let named = |name: &str| design(json!([body(name, 6000.0, [0.0; 3], [30.0, 20.0, 10.0])]), json!([]));
assert!(pass(8, &empty, &named("Bracket"), &[]).is_empty());
assert!(!pass(8, &empty, &named("Body1"), &[]).is_empty());
// 9: unchanged, and the reply gives the volume.
let start = named("Body1");
let said = |text: &str| [json!({"user": "What is the volume of the part?"}), json!({"agent": text, "thought": false})];
let mut read = start.clone();
read["revision"] = json!(7);
read["dirty"] = json!(false);
read["selection"] = json!([{"body": "Body1"}]);
assert!(pass(9, &start, &read, &said("The volume is 6000 mm³.")).is_empty());
assert!(!pass(9, &start, &read, &said("About 60 cm³.")).is_empty());
assert!(!pass(9, &start, &named("Bracket"), &said("6 000 mm³")).is_empty());
let mut undone = start.clone();
undone["undo"] = json!(2);
assert!(!pass(9, &start, &undone, &said("6000 mm³")).is_empty());
// 11: any change counts; reading does not.
assert!(pass(11, &start, &named("Bracket"), &[]).is_empty());
assert!(pass(11, &start, &undone, &[]).is_empty());
assert!(!pass(11, &start, &read, &[]).is_empty());
// An unreadable design passes nothing.
assert!(!pass(11, &start, &json!({}), &[]).is_empty());
}
#[test]
fn the_volume_reply() {
for yes in ["6000", "6 000 mm³", "6,000", "6.000", "6\u{202f}000", "6 cm³", "6.00cm3", "is 6000."] {
assert!(volume_reply(yes), "{yes}");
}
for no in ["60000", "6,0001", "60 cm³", "600", "6.01 cm³", ""] {
assert!(!volume_reply(no), "{no}");
}
}
#[test]
fn failure_categories() {
for (items, expected) in [
(
json!([{"user": "Make a 20 mm cube."}, {"agent": "{\"name\": \"execute\", \"arguments\": {}}", "thought": false}]),
"wrote the call as text",
),
(
json!([{"notice": "HTTP 400: registry.ollama.ai/library/gemma3:4b does not support tools", "error": true}]),
"the endpoint refused tools (HTTP 400)",
),
(json!([{"user": "Make a 20 mm cube."}, {"agent": "", "thought": false}]), "an empty reply"),
(json!([{"agent": "Let me think", "thought": true}]), "an empty reply"),
(json!([{"agent": "Sure, here is how to make a cube.", "thought": false}]), "only talked"),
(
json!([{"tool": "1", "status": "Failed", "output": "unknown command `solid.cube` (did you mean `solid.box`?)"}]),
"wrong or unknown command",
),
(json!([{"tool": "1", "status": "Failed", "output": "unknown tool `make_cube`"}]), "wrong or unknown command"),
(json!([{"tool": "1", "status": "Failed", "output": "invalid parameters for `solid.box`: `length` is required"}]), "wrong arguments"),
(json!([{"tool": "1", "status": "Failed", "output": "Not run: the tool arguments were not valid JSON"}]), "wrong arguments"),
(json!([{"tool": "1", "status": "Failed", "output": "no body `Box`"}]), "gave up after errors"),
(
json!([{"tool": "1", "status": "Completed", "output": "{\"ok\": false, \"failed\": {\"error\": \"unknown command `solid.delete`\"}}"}]),
"wrong or unknown command",
),
(
json!([{"tool": "1", "status": "Completed"}, {"agent": "\n(stopped after 24 requests for one message; send another message to go on)"}]),
"hit the request limit",
),
(json!([{"tool": "1", "status": "Completed", "output": "{}"}]), "wrong result"),
] {
let items = items.as_array().unwrap();
assert_eq!(classify(items, false), expected, "{items:?}");
assert_eq!(classify(items, true), "timeout");
}
}
#[test]
fn markdown_is_the_comment_template() {
let rows: Vec<Value> =
(1..=11).map(|n| json!({"task": n, "repeat": 1, "passed": n != 2, "failure": if n == 2 { "an empty reply" } else { "" }})).collect();
let report = markdown("qwen2.5:7b", "0.19.0", &rows, 1);
assert!(report.starts_with(
"### qwen2.5:7b (<quant>, <download size>)\nRuntime: <Ollama/LM Studio/... version>, context <num_ctx if changed>\nSolidCraft: 0.19.0\nResult: 10/11\n| # | pass/fail | how it failed |\n|---|---|---|\n| 1 | pass | |\n| 2 | fail | an empty reply |\n"
));
assert!(report.ends_with("| 11 | pass | |\nNotes: <anything odd, e.g. very slow, needed a retry>\n"));
let partial = markdown("m|x", "v", &rows[..1], 1);
assert!(partial.starts_with("### m x ("));
assert!(partial.contains("Result: 1/11") && partial.contains("| 2 | | not run |") && partial.contains("partial run"));
}
#[test]
fn options_are_checked_before_connecting() {
for extra in [
&["--repeat", "0"][..],
&["--repeat", "101"],
&["--timeout-s", "3601"],
&["--tasks", "0"],
&["--tasks", "12"],
&["--tasks", "1,1"],
&["--model", "again"],
&["--unexpected", "1"],
&["--token"],
] {
let mut args: Vec<String> =
["--connect", "127.0.0.1:9", "--model", "m", "--base-url", "http://127.0.0.1:11434/v1"].iter().map(|s| s.to_string()).collect();
args.extend(extra.iter().map(|s| s.to_string()));
assert!(options(&args).is_err(), "{extra:?}");
}
let args: Vec<String> =
["--connect", "127.0.0.1:9", "--model", "m", "--base-url", "u", "--tasks", "2, 5"].iter().map(|s| s.to_string()).collect();
let o = options(&args).unwrap();
assert_eq!((o.tasks, o.repeat, o.timeout), (vec![2, 5], 1, Duration::from_secs(300)));
assert!(Control::connect("10.0.0.1:9271", Some("t")).is_err());
assert!(Control::connect("localhost", Some("t")).is_err());
}
}
+6 -3
View File
@@ -6,11 +6,14 @@ use std::sync::Mutex;
use serde_json::{Value, json};
use solidcraft_engine::crash;
const USAGE: &str = "usage: solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]";
/// The command's usage, from the same lines the top-level usage lists.
fn usage() -> String {
crate::command_usage("crashes")
}
pub fn run(args: &[String]) -> Result<(), String> {
let dir = crash::default_dir().ok_or("no crash folder on this system")?;
let id = || args.get(1).map(String::as_str).ok_or_else(|| USAGE.to_string());
let id = || args.get(1).map(String::as_str).ok_or_else(usage);
match args.first().map(String::as_str) {
None | Some("list") => {
let reports = crash::list(&dir);
@@ -43,7 +46,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
println!("deleted {} report(s)", crash::clear(&dir, args.get(1).map(String::as_str)));
Ok(())
}
_ => Err(USAGE.to_string()),
_ => Err(usage()),
}
}
+10
View File
@@ -16,9 +16,16 @@ fn flag<T: std::str::FromStr>(args: &[String], name: &str, default: T) -> T {
args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).and_then(|v| v.parse().ok()).unwrap_or(default)
}
/// The options `fuzz` takes, and the ones that stand alone.
const OPTIONS: &[&str] = &["--from", "--count", "--jobs", "--timeout", "--steps", "--out", "--report"];
const SWITCHES: &[&str] = &["--minimise"];
/// One seed: a design and a boolean case, as JSON on stdout (progress on stderr).
pub fn one(args: &[String]) -> Result<(), String> {
let seed: u64 = args.first().and_then(|s| s.parse().ok()).ok_or("fuzz-one <seed> [--steps N] [--minimise]")?;
// The seed is the first argument; the rest are options, and an option `fuzz-one` doesn't
// take is refused rather than ignored.
crate::known_args("fuzz-one", &args[1..], &["--steps"], SWITCHES, false)?;
let steps = flag(args, "--steps", 8usize);
let minimise = args.iter().any(|a| a == "--minimise");
// Panics caught inside the kernel: harmless natively, fatal in the web build.
@@ -64,6 +71,9 @@ struct Job {
/// Run seeds `--from`…`--from + --count` in `--jobs` processes, `--timeout` seconds each;
/// write failing scripts to `--out` and a summary to `--report`.
pub fn run(args: &[String]) -> Result<(), String> {
// An option `fuzz` doesn't take is refused before a seed runs: it would otherwise be
// ignored, and the run would go on for minutes with the wrong settings.
crate::known_args("fuzz", args, OPTIONS, SWITCHES, false)?;
let from = flag(args, "--from", 0u64);
let count = flag(args, "--count", 100u64);
let last = from.checked_add(count.saturating_sub(1)).ok_or("seed range exceeds u64::MAX: reduce --from or --count")?;
+175 -63
View File
@@ -6,13 +6,16 @@
//! solidcraft-cli script <design.solidcraft> [--out script.json] the design's history as a command script
//! solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json] loading timings as JSON lines
//! solidcraft-cli bench-agent [--scale F] [--save F] [--script-out F] a ~1,300-body design built command by command, timed
//! solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N]
//! [--timeout-s 300] [--json out.json] [--markdown out.md]
//! the standard Assistant model test (#4851) on a running app
//! solidcraft-cli snapshot <script|design> [--in design|part.step] --out shot.png [--width W] [--height H] [--view iso|front|top|…|NAME | --camera JSON] [--no-grid] [--no-axes]
//! [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]
//! solidcraft-cli exec <command> [json-params] run one command on an empty design
//! solidcraft-cli commands the command registry as JSON
//! solidcraft-cli recipe <recipe.json> translate an oracle recipe to a script
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
//! solidcraft-cli mcp [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
//! [--param-check off|warn|strict]
//! MCP server on stdio (docs/mcp.md)
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
@@ -24,6 +27,7 @@
#![forbid(unsafe_code)]
mod bench_agent;
mod bench_assistant;
mod crashes;
mod fuzz;
mod logger;
@@ -42,57 +46,98 @@ mod app_icon;
use solidcraft_engine::Session;
use solidcraft_engine::view::Guides;
const USAGE: &str = "usage:
solidcraft-cli run <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]
solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...
(a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)
(--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;
--profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)
solidcraft-cli eval <script.json|design.solidcraft|part.step> [--json-out FILE] (alias: inspect; or --in FILE)
(--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,
as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)
solidcraft-cli script <design.solidcraft|script.json> [--out script.json]
(the design's history as a command script that eval and run replay)
solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]
(--ops: after opening, run each command of the script and undo it, timing the command, the
evaluations it caused and the view it leaves: placing and meshing what changed)
solidcraft-cli bench-agent [--scale F] [--save design.solidcraft] [--script-out script.json]
solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]
[--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]
solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...
(as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;
--view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;
--camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);
--no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;
--body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside
it shows) and color #rrggbb, either or both, once per body;
--tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body
is visible inside the material it meets; a later --body overrides it;
--section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world
coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)
solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]
solidcraft-cli exec <command> [json-params]
solidcraft-cli commands
solidcraft-cli recipe <recipe.json>
solidcraft-cli oracle <case-dir>... [--json]
solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]
solidcraft-cli mcp [--in design.solidcraft|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
[--param-check off|warn|strict]
solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]
solidcraft-cli licences
solidcraft-cli gpu [--policy auto|hardware|software|cpu]
(the GPU adapters a headless render can open, best first, the one the policy picks and the tier it
lands on: gpu, gpu-software, or the CPU rasterizer (auto with no adapter, or cpu); the policy defaults
to SOLIDCRAFT_GPU, else auto; exits with an error when hardware or software finds no such adapter)
solidcraft-cli icon --out icon.png [--size 256] | --out icon.ico
--debug, with any command: what SolidCraft logs (why a body has no display plan, why a save left its
model cache out), on stderr; RUST_LOG=error|warn|info|debug|trace sets one level for every crate
";
/// One command the CLI takes: the name to type, the usage it prints for `--help`,
/// and whether the top-level usage lists it (an alias answers to another command's name).
struct Command {
name: &'static str,
usage: &'static str,
listed: bool,
}
/// `run`'s lines in the usage.
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\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";
/// `eval`'s lines in the usage.
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.solidcraft|part.step> [--json-out FILE] (alias: inspect; or --in FILE)\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";
/// `script`'s lines in the usage.
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.solidcraft|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
/// `bench-open`'s lines in the usage.
const USAGE_BENCH_OPEN: &str = " solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
/// `bench-agent`'s lines in the usage.
const USAGE_BENCH_AGENT: &str = " solidcraft-cli bench-agent [--scale F] [--save design.solidcraft] [--script-out script.json]\n";
/// `bench-assistant`'s lines in the usage.
const USAGE_BENCH_ASSISTANT: &str = " solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N] [--timeout-s 300] [--json out.json] [--markdown out.md]\n";
/// `snapshot`'s lines in the usage.
const USAGE_SNAPSHOT: &str = " solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
/// `render`'s lines in the usage.
const USAGE_RENDER: &str = " solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]\n [--progressive] [--samples N] [--max-depth N] [--seed N] [--exposure EV] [--lighting FILE.json] [--environment FILE.json] [--tone-map reinhard|aces_fitted]\n";
/// `exec`'s lines in the usage.
const USAGE_EXEC: &str = " solidcraft-cli exec <command> [json-params]\n";
/// `commands`'s lines in the usage.
const USAGE_COMMANDS: &str = " solidcraft-cli commands\n";
/// `recipe`'s lines in the usage.
const USAGE_RECIPE: &str = " solidcraft-cli recipe <recipe.json>\n";
/// `oracle`'s lines in the usage.
const USAGE_ORACLE: &str = " solidcraft-cli oracle <case-dir>... [--json]\n";
/// `fuzz`'s lines in the usage.
const USAGE_FUZZ: &str =
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
/// `mcp`'s lines in the usage.
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.solidcraft|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";
/// `crashes`'s lines in the usage.
const USAGE_CRASHES: &str = " solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
/// `licences`'s lines in the usage.
const USAGE_LICENCES: &str = " solidcraft-cli licences\n";
/// `gpu`'s lines in the usage.
const USAGE_GPU: &str = " solidcraft-cli gpu [--policy auto|hardware|software|cpu]\n (the GPU adapters a headless render can open, best first, the one the policy picks and the tier it\n lands on: gpu, gpu-software, or the CPU rasterizer (auto with no adapter, or cpu); the policy defaults\n to SOLIDCRAFT_GPU, else auto; exits with an error when hardware or software finds no such adapter)\n";
/// `icon`'s lines in the usage.
const USAGE_ICON: &str = " solidcraft-cli icon --out icon.png [--size 256] | --out icon.ico\n";
/// `fuzz-one`'s lines in the usage: a seed and the flags of one fuzz run.
const USAGE_FUZZ_ONE: &str = " solidcraft-cli fuzz-one <seed> [--steps N] [--minimise]\n";
/// `bench-edit`'s lines in the usage: the parameter a script changes.
const USAGE_BENCH_EDIT: &str = " solidcraft-cli bench-edit <script.json|recipe.json> <param> <expr>\n";
/// `save-stress`'s lines in the usage: the file saved over and over.
const USAGE_SAVE_STRESS: &str = " solidcraft-cli save-stress <path> <rounds>\n";
/// The flags every command takes, at the end of the top-level usage.
const USAGE_DEBUG: &str = " --debug, with any command: what SolidCraft logs (why a body has no display plan, why a save left its\n model cache out), on stderr; RUST_LOG=error|warn|info|debug|trace sets one level for every crate\n";
/// The commands the CLI takes, in the order the top-level usage lists them.
const COMMANDS: &[Command] = &[
Command { name: "run", usage: USAGE_RUN, listed: true },
Command { name: "eval", usage: USAGE_EVAL, listed: true },
Command { name: "inspect", usage: USAGE_EVAL, listed: false },
Command { name: "script", usage: USAGE_SCRIPT, listed: true },
Command { name: "bench-open", usage: USAGE_BENCH_OPEN, listed: true },
Command { name: "bench-agent", usage: USAGE_BENCH_AGENT, listed: true },
Command { name: "bench-assistant", usage: USAGE_BENCH_ASSISTANT, listed: true },
Command { name: "snapshot", usage: USAGE_SNAPSHOT, listed: true },
Command { name: "render", usage: USAGE_RENDER, listed: true },
Command { name: "exec", usage: USAGE_EXEC, listed: true },
Command { name: "commands", usage: USAGE_COMMANDS, listed: true },
Command { name: "recipe", usage: USAGE_RECIPE, listed: true },
Command { name: "oracle", usage: USAGE_ORACLE, listed: true },
Command { name: "fuzz", usage: USAGE_FUZZ, listed: true },
Command { name: "fuzz-one", usage: USAGE_FUZZ_ONE, listed: false },
Command { name: "mcp", usage: USAGE_MCP, listed: true },
Command { name: "crashes", usage: USAGE_CRASHES, listed: true },
Command { name: "licences", usage: USAGE_LICENCES, listed: true },
Command { name: "licenses", usage: USAGE_LICENCES, listed: false },
Command { name: "gpu", usage: USAGE_GPU, listed: true },
Command { name: "icon", usage: USAGE_ICON, listed: true },
Command { name: "bench-edit", usage: USAGE_BENCH_EDIT, listed: false },
Command { name: "save-stress", usage: USAGE_SAVE_STRESS, listed: false },
];
fn main() -> ExitCode {
let mut args: Vec<String> = std::env::args().skip(1).collect();
logger::init(&mut args);
let r = match args.first().map(String::as_str) {
// Asked-for help is not an error: the command's usage on stdout, exit 0. It is read
// before the command runs, so `--help` never starts a run (a fuzz run, a server).
Some(cmd) if wants_help(&args[1..]) && is_command(cmd) => {
use std::io::Write as _;
let _ = std::io::stdout().lock().write_all(command_usage(cmd).as_bytes());
Ok(())
}
Some("run") => cmd_run(&args[1..]),
Some("eval" | "inspect") => cmd_eval(&args[1..]),
Some("script") => cmd_script(&args[1..]),
@@ -113,7 +158,7 @@ fn main() -> ExitCode {
}
})
}
Some("recipe") => args.get(1).ok_or_else(|| USAGE.to_string()).and_then(|p| {
Some("recipe") => args.get(1).ok_or_else(usage).and_then(|p| {
let v = read_json(p)?;
recipe::to_script(&v).map(|s| println!("{}", pretty(&s)))
}),
@@ -136,6 +181,8 @@ fn main() -> ExitCode {
Some("bench-open") => cmd_bench_open(&args[1..]),
// A ~1,300-body design built command by command, as an agent builds one over MCP.
Some("bench-agent") => bench_agent::run(&args[1..]),
// The standard Assistant model test (#4851), run against a desktop app over its control channel.
Some("bench-assistant") => bench_assistant::run(&args[1..]),
Some("--version" | "-V") => {
println!("solidcraft-cli {}", env!("CARGO_PKG_VERSION"));
Ok(())
@@ -143,10 +190,10 @@ fn main() -> ExitCode {
// Asked-for help is not an error: usage on stdout, exit 0.
Some("--help" | "-h" | "help") => {
use std::io::Write as _;
let _ = std::io::stdout().lock().write_all(USAGE.as_bytes());
let _ = std::io::stdout().lock().write_all(usage().as_bytes());
Ok(())
}
_ => Err(USAGE.to_string()),
_ => Err(usage()),
};
match r {
Ok(()) => ExitCode::SUCCESS,
@@ -161,6 +208,35 @@ fn pretty(v: &Value) -> String {
serde_json::to_string_pretty(v).unwrap_or_default()
}
/// The whole usage: every command's lines under one `usage:` line. The lines come from the
/// same place a command's own `--help` prints, so the two cannot drift apart.
fn usage() -> String {
let mut s = String::from("usage:\n");
for c in COMMANDS.iter().filter(|c| c.listed) {
s.push_str(c.usage);
}
s.push_str(USAGE_DEBUG);
s
}
/// A command's own usage: its lines under a `usage:` line.
fn command_usage(cmd: &str) -> String {
match COMMANDS.iter().find(|c| c.name == cmd) {
Some(c) => format!("usage:\n{}", c.usage),
None => usage(),
}
}
/// Whether the name is a command the CLI takes.
fn is_command(cmd: &str) -> bool {
COMMANDS.iter().any(|c| c.name == cmd)
}
/// Whether the arguments ask for help.
fn wants_help(args: &[String]) -> bool {
args.iter().any(|a| a == "--help" || a == "-h")
}
pub fn read_json(path: &str) -> Result<Value, String> {
// Through the engine's file access, which refuses anything that is not a regular file and
// reads no more than the cap: a character device or a FIFO would otherwise never end.
@@ -266,7 +342,7 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
let base = flag(args, "--in");
let path = positional(args);
if path.is_none() && base.is_none() {
return Err(USAGE.to_string());
return Err(usage());
}
let want_results = args.iter().any(|a| a == "--results");
// `--profile` times each command and splits out its kernel time; it implies `--timings`.
@@ -326,13 +402,14 @@ fn open(base: Option<&str>, path: Option<&String>) -> Result<Session, String> {
}
(Some(b), None) => load(b),
(None, Some(p)) => load(p),
(None, None) => Err(USAGE.to_string()),
(None, None) => Err(usage()),
}
}
/// Refuses an option `cmd` doesn't take, and one missing its value: a typo, or another
/// command's option, would otherwise be ignored without a word.
fn known_flags(cmd: &str, args: &[String], valued: &[&str], switches: &[&str]) -> Result<(), String> {
/// command's option, would otherwise be ignored without a word. `positionals` says whether the
/// command takes a word of its own (a file, a seed): `fuzz` takes none, and would start a run.
fn known_args(cmd: &str, args: &[String], valued: &[&str], switches: &[&str], positionals: bool) -> Result<(), String> {
let mut i = 0;
while let Some(a) = args.get(i) {
if valued.contains(&a.as_str()) {
@@ -340,7 +417,9 @@ fn known_flags(cmd: &str, args: &[String], valued: &[&str], switches: &[&str]) -
Some(v) if !v.starts_with("--") => i += 2,
_ => return Err(format!("{cmd}: {a} needs a value")),
}
} else if a.starts_with("--") && !switches.contains(&a.as_str()) {
} else if switches.contains(&a.as_str()) {
i += 1;
} else if a.starts_with("--") || !positionals {
return Err(format!("{cmd} doesn't take {a} (it takes {})", [valued, switches].concat().join(", ")));
} else {
i += 1;
@@ -364,7 +443,7 @@ fn timeline_ok(s: &Session) -> Result<(), String> {
fn cmd_eval(args: &[String]) -> Result<(), String> {
let json_out = json_out(args)?;
let path = flag(args, "--in").or_else(|| positional(args).map(String::as_str)).ok_or_else(|| USAGE.to_string())?;
let path = flag(args, "--in").or_else(|| positional(args).map(String::as_str)).ok_or_else(usage)?;
let mut s = load(path)?;
let m = s.execute("inspect.measure", &json!({})).map_err(|e| e.to_string())?;
let d = s.execute("document.inspect", &json!({})).map_err(|e| e.to_string())?;
@@ -382,7 +461,7 @@ fn cmd_eval(args: &[String]) -> Result<(), String> {
/// A design's history as a command script (`timeline.export_script`): printed, or written to
/// `--out`; what the script leaves out goes to stderr.
fn cmd_script(args: &[String]) -> Result<(), String> {
let path = positional(args).ok_or_else(|| USAGE.to_string())?;
let path = positional(args).ok_or_else(usage)?;
let s = &mut load(path)?;
let out = flag(args, "--out");
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
@@ -443,14 +522,15 @@ fn install_renderer() {
fn cmd_snapshot(args: &[String]) -> Result<(), String> {
install_renderer();
known_flags(
known_args(
"snapshot",
args,
&["--in", "--out", "--width", "--height", "--view", "--camera", "--body", "--tool", "--section"],
&["--no-grid", "--no-axes"],
true,
)?;
if positional(args).is_none() && flag(args, "--in").is_none() {
return Err(USAGE.to_string());
return Err(usage());
}
let out = flag(args, "--out").ok_or("snapshot needs --out FILE.png")?;
let size = |name: &str, default: u32| match flag(args, name) {
@@ -534,7 +614,7 @@ fn ico(images: &[(usize, Vec<u8>)]) -> Result<Vec<u8>, String> {
fn cmd_exec(args: &[String]) -> Result<(), String> {
install_renderer();
let id = args.first().ok_or_else(|| USAGE.to_string())?;
let id = args.first().ok_or_else(usage)?;
let p: Value = match args.get(1) {
Some(t) => serde_json::from_str(t).map_err(|e| format!("params: {e}"))?,
None => json!({}),
@@ -549,6 +629,10 @@ fn cmd_exec(args: &[String]) -> Result<(), String> {
/// a running app's control port. stdout carries only protocol messages. Files the agent's tool
/// calls reach are confined to `solidcraft_mcp::agent_scope` unless `--allow-any-path` is given.
fn cmd_mcp(args: &[String]) -> Result<(), String> {
let render_gpu = args.iter().any(|arg| arg == "--render-gpu");
if render_gpu && flag(args, "--connect").is_some() {
return Err("mcp: --render-gpu installs a headless image host; a connected application owns its own device".into());
}
install_renderer();
let scope = (!args.iter().any(|a| a == "--allow-any-path"))
.then(|| solidcraft_mcp::agent_scope(flags(args, "--allow-path").into_iter().map(std::path::PathBuf::from)));
@@ -592,18 +676,45 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
}
Box::new(remote)
}
(None, Some(path)) => Box::new(solidcraft_mcp::Headless::new(load_checked(path, param_check)?)),
(None, Some(path)) => {
let mut headless = solidcraft_mcp::Headless::new(load_checked(path, param_check)?);
if render_gpu {
headless.session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
}
Box::new(headless)
}
(None, None) => {
if !args.iter().any(|a| a == "--headless")
&& let Some(port) = discover_app()
{
eprintln!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
return serve_mcp(Box::new(solidcraft_mcp::Remote::new(&format!("127.0.0.1:{port}"), flag(args, "--token"))), scope);
}
let mut headless = solidcraft_mcp::Headless::default();
headless.session.param_check = param_check;
if render_gpu {
headless.session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
}
Box::new(headless)
}
};
serve_mcp(backend, scope)
}
fn serve_mcp(backend: Box<dyn solidcraft_mcp::Backend>, scope: Option<solidcraft_engine::io::vfs::PathScope>) -> Result<(), String> {
let stdin = std::io::stdin();
let stdout = std::io::stdout();
solidcraft_mcp::Server::new(backend).with_path_scope(scope).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
}
/// The port of the running desktop app: the one its discovery file names, if something listens
/// there (a file left by an app that crashed names a closed port).
fn discover_app() -> Option<u16> {
let port = solidcraft_engine::control_auth::read_current_port()?;
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], port));
std::net::TcpStream::connect_timeout(&addr, std::time::Duration::from_millis(500)).ok().map(|_| port)
}
/// `save-stress <path> <rounds>`: save a design of a few hundred kilobytes `rounds` times, its
/// parameter `save_round` set to the round number each time.
fn cmd_save_stress(args: &[String]) -> Result<(), String> {
@@ -716,9 +827,10 @@ pub(crate) fn cmd_bench_open(args: &[String]) -> Result<(), String> {
let t = std::time::Instant::now();
let s = Session::opened(doc, saved.as_ref());
drop(read);
// Taken from the file: nothing evaluated (SOLIDCRAFT_NO_MODEL_CACHE evaluates anyway).
// Taken from the file: nothing evaluated (SOLIDCRAFT_NO_MODEL_CACHE evaluates anyway), or
// only the features whose state a thinned file left out.
let adopted = saved.is_some() && s.model.last_recomputed == 0;
println!("{}", json!({"step": "evaluate", "ms": t.elapsed().as_secs_f64() * 1000.0, "adopted": adopted}));
println!("{}", json!({"step": "evaluate", "ms": t.elapsed().as_secs_f64() * 1000.0, "adopted": adopted, "recomputed": s.model.last_recomputed}));
for r in &s.model.results {
println!("{}", json!({"step": "feature", "id": r.id, "name": r.name, "ms": r.ms, "error": r.error}));
}
+108 -18
View File
@@ -1,25 +1,89 @@
//! Complete headless image-job workflow through the same engine commands as the UI/MCP.
use serde_json::json;
use serde_json::{Value, json};
use solidcraft_engine::{
doc::render_settings::RenderSetup,
render::job::{CpuHost, JobId, Phase},
Session,
doc::render_settings::{RenderJob, RenderSetup},
render::job::{CpuHost, JobId, Phase, ToneMap},
};
/// Only explicit runtime CLI flags are stored here. A typed setup owns these
/// values even when a supplied flag happens to match the saved value.
#[derive(Default)]
struct RuntimeOptions {
progressive: bool,
transparent: bool,
samples: Option<u32>,
max_depth: Option<u32>,
seed: Option<u32>,
lighting: Option<String>,
tone_map: Option<ToneMap>,
environment: Option<String>,
}
impl RuntimeOptions {
fn explicit_flag(&self) -> Option<&'static str> {
[
(self.progressive, "--progressive"),
(self.transparent, "--transparent"),
(self.samples.is_some(), "--samples"),
(self.max_depth.is_some(), "--max-depth"),
(self.seed.is_some(), "--seed"),
(self.lighting.is_some(), "--lighting"),
(self.tone_map.is_some(), "--tone-map"),
(self.environment.is_some(), "--environment"),
]
.into_iter()
.find_map(|(present, flag)| present.then_some(flag))
}
fn request(&self, session: &Session, settings: RenderSetup) -> Result<(Value, bool), String> {
if let Some(job) = &settings.job {
if let Some(flag) = self.explicit_flag() {
return Err(format!("{flag} cannot accompany a typed saved render setup; change the saved job options instead"));
}
settings.validate().map_err(|e| e.to_string())?;
session.doc.render.validate_resources(&settings).map_err(|e| e.to_string())?;
let needs_gpu = matches!(job, RenderJob::GpuPath { .. });
return Ok((json!({"settings":settings}), needs_gpu));
}
// Legacy setups and unnamed requests retain their runtime wire contract.
settings.validate().map_err(|e| e.to_string())?;
let mut params = json!({"settings":settings,"transparent_background":self.transparent,
"progressive":self.progressive,"samples":self.samples,"max_depth":self.max_depth,"seed":self.seed});
if let Some(path) = &self.lighting {
let text = solidcraft_engine::io::vfs::read_to_string(path, 65536).map_err(|e| format!("could not read lighting: {e}"))?;
let lighting: solidcraft_engine::render::job::StudioLighting = serde_json::from_str(&text).map_err(|e| e.to_string())?;
params["lighting"] = json!(lighting);
}
if let Some(tone_map) = self.tone_map {
params["tone_map"] = json!(tone_map);
}
if let Some(path) = &self.environment {
let text = solidcraft_engine::io::vfs::read_to_string(path, 65536).map_err(|e| format!("could not read environment: {e}"))?;
params["environment"] = serde_json::from_str(&text).map_err(|e| e.to_string())?;
}
Ok((params, self.progressive))
}
}
pub fn run(args: &[String]) -> Result<(), String> {
let input = args.first().filter(|p| !p.starts_with('-')).ok_or("render requires an input design or script")?;
let mut out = None;
let mut setup = None;
let mut width = None;
let mut height = None;
let mut transparent = false;
let mut exposure = None;
let mut runtime = RuntimeOptions::default();
let mut overwrite = false;
let mut timeout = 120u64;
let mut i = 1;
while let Some(arg) = args.get(i) {
match arg.as_str() {
"--transparent" => transparent = true,
"--progressive" => runtime.progressive = true,
"--transparent" => runtime.transparent = true,
"--overwrite" => overwrite = true,
"--out" | "--setup" | "--width" | "--height" | "--timeout" => {
"--out" | "--setup" | "--width" | "--height" | "--timeout" | "--samples" | "--max-depth" | "--seed" | "--exposure" | "--lighting"
| "--tone-map" | "--environment" => {
i += 1;
let value = args.get(i).ok_or_else(|| format!("missing value for {arg}"))?;
match arg.as_str() {
@@ -27,6 +91,20 @@ pub fn run(args: &[String]) -> Result<(), String> {
"--setup" => setup = Some(value.clone()),
"--width" => width = Some(value.parse::<u32>().map_err(|_| "invalid width")?),
"--height" => height = Some(value.parse::<u32>().map_err(|_| "invalid height")?),
"--samples" => runtime.samples = Some(value.parse::<u32>().map_err(|_| "invalid samples")?),
"--max-depth" => runtime.max_depth = Some(value.parse::<u32>().map_err(|_| "invalid depth")?),
"--seed" => runtime.seed = Some(value.parse::<u32>().map_err(|_| "invalid seed")?),
"--exposure" => exposure = Some(value.parse::<f64>().map_err(|_| "invalid exposure")?),
// Defer file reads until a saved job has rejected conflicting controls.
"--environment" => runtime.environment = Some(value.clone()),
"--lighting" => runtime.lighting = Some(value.clone()),
"--tone-map" => {
runtime.tone_map = Some(match value.as_str() {
"reinhard" => ToneMap::Reinhard,
"aces_fitted" => ToneMap::AcesFitted,
_ => return Err("tone-map must be reinhard or aces_fitted".into()),
})
}
_ => timeout = value.parse::<u64>().map_err(|_| "invalid timeout")?,
}
}
@@ -39,18 +117,10 @@ pub fn run(args: &[String]) -> Result<(), String> {
}
let out = out.ok_or("render requires --out FILE.png")?;
let mut session = crate::load(input)?;
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
session.execute("render.scene", &json!({})).map_err(|e| e.to_string())?;
let mut settings = if let Some(name) = setup {
session.doc.render.setups.get(&name).cloned().ok_or("no saved render setup with that name")?
let mut settings = if let Some(name) = setup.as_deref() {
session.doc.render.setups.get(name).cloned().ok_or("no saved render setup with that name")?
} else {
let mut settings = RenderSetup::default();
if let Some(camera) = session.camera {
settings.camera = camera;
} else {
settings.camera.fit(&solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?.bounds);
}
settings
RenderSetup::default()
};
if let Some(width) = width {
settings.width = width;
@@ -58,7 +128,27 @@ pub fn run(args: &[String]) -> Result<(), String> {
if let Some(height) = height {
settings.height = height;
}
let params = json!({"settings":settings,"transparent_background":transparent});
if let Some(exposure) = exposure {
settings.exposure_ev = exposure;
}
// Resolve saved intent before opening a GPU or explicitly settling geometry.
// Width/height/exposure only change this validated inline settings clone.
let (mut params, needs_gpu) = runtime.request(&session, settings)?;
session.set_render_job_host(Some(CpuHost::for_platform().map_err(|e| e.to_string())?)).map_err(|e| e.to_string())?;
if needs_gpu {
session.register_render_job_host(solidcraft_gpu::path::host().map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
}
session.execute("render.scene", &json!({})).map_err(|e| e.to_string())?;
if setup.is_none() {
let camera = if let Some(camera) = session.camera {
camera
} else {
let mut camera = RenderSetup::default().camera;
camera.fit(&solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?.bounds);
camera
};
params["settings"]["camera"] = json!(camera);
}
let preflight = session.execute("render.preflight", &params).map_err(|e| e.to_string())?;
let started = session.execute("render.start", &params).map_err(|e| e.to_string())?;
let id: JobId = serde_json::from_value(started["job"].clone()).map_err(|e| e.to_string())?;
+1 -1
View File
@@ -1412,7 +1412,7 @@ fn a_sketch_on_a_patterned_boss_top() {
fn construction_geometry_and_holes_on_a_moved_component() {
let moved = || {
vec![
json!({"command": "component.create", "params": {"name": "Arm"}}),
json!({"command": "component.create", "params": {"name": "Arm", "grounded": false}}),
json!({"command": "solid.box", "params": {"corner": [20, 0, 0], "length": 10, "width": 4, "height": 4, "body_name": "ArmBody"}}),
json!({"command": "component.activate", "params": {"component": 0}}),
json!({"command": "occurrence.move", "params": {"occurrence": "Arm:1", "translate": [0, 50, 0]}}),
@@ -1,6 +1,6 @@
{"commands": [
{"command": "solid.box", "params": {"length": 10, "width": 10, "height": 10, "body_name": "Base"}},
{"command": "component.create", "params": {"name": "Arm"}},
{"command": "component.create", "params": {"name": "Arm", "grounded": false}},
{"command": "solid.box", "params": {"corner": [20, 0, 0], "length": 10, "width": 4, "height": 4, "body_name": "ArmBody"}},
{"command": "solid.fillet", "params": {"edges": [[25, 4, 4]], "radius": 1}},
{"command": "component.activate", "params": {"component": 0}},
+137 -1
View File
@@ -1,6 +1,84 @@
//! An explicit help request prints usage to stdout and succeeds; an unknown command still fails.
use std::process::Command;
use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
/// Every command the CLI takes, with the name its own usage names: `inspect` is `eval`, and
/// `licenses` is `licences`.
const COMMANDS: &[(&str, &str)] = &[
("run", "run"),
("eval", "eval"),
("inspect", "eval"),
("script", "script"),
("bench-open", "bench-open"),
("bench-agent", "bench-agent"),
("snapshot", "snapshot"),
("render", "render"),
("exec", "exec"),
("commands", "commands"),
("recipe", "recipe"),
("oracle", "oracle"),
("fuzz", "fuzz"),
("fuzz-one", "fuzz-one"),
("mcp", "mcp"),
("crashes", "crashes"),
("licences", "licences"),
("licenses", "licences"),
("gpu", "gpu"),
("icon", "icon"),
("bench-edit", "bench-edit"),
("save-stress", "save-stress"),
];
/// The commands the usage lists: the aliases answer to another command's name.
const LISTED: &[&str] = &[
"run",
"eval",
"script",
"bench-open",
"bench-agent",
"snapshot",
"render",
"exec",
"commands",
"recipe",
"oracle",
"fuzz",
"mcp",
"crashes",
"licences",
"gpu",
"icon",
];
/// The command names a text mentions after `solidcraft-cli `, each one only once.
fn mentioned(text: &str) -> Vec<&str> {
let mut names: Vec<&str> = text.split("solidcraft-cli ").skip(1).filter_map(|rest| rest.split_whitespace().next()).collect();
names.sort_unstable();
names.dedup();
names
}
/// Runs the binary and waits at most `limit` for it to finish. `None` means it was still
/// running and was killed: the fuzz run `fuzz --help` used to start goes on for minutes.
fn run_within(limit: Duration, args: &[&str]) -> Option<(Option<i32>, String, String)> {
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args(args).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn().unwrap();
let started = Instant::now();
loop {
match child.try_wait().unwrap() {
Some(status) => {
let out = child.wait_with_output().unwrap();
return Some((status.code(), String::from_utf8_lossy(&out.stdout).into_owned(), String::from_utf8_lossy(&out.stderr).into_owned()));
}
None if started.elapsed() >= limit => {
let _ = child.kill();
let _ = child.wait();
return None;
}
None => std::thread::sleep(Duration::from_millis(25)),
}
}
}
#[test]
fn help_prints_usage_and_succeeds() {
@@ -21,3 +99,61 @@ fn unknown_command_still_fails_with_usage() {
assert!(output.stdout.is_empty());
assert!(String::from_utf8_lossy(&output.stderr).starts_with("error: usage:"));
}
#[test]
fn every_command_prints_its_own_usage() {
for (cmd, named) in COMMANDS {
for flag in ["--help", "-h"] {
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args([*cmd, flag]).output().unwrap();
assert!(output.status.success(), "{cmd} {flag}: {}", String::from_utf8_lossy(&output.stderr));
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.starts_with("usage:"), "{cmd} {flag}: {stdout}");
assert_eq!(mentioned(&stdout), vec![*named], "{cmd} {flag} printed another command's usage: {stdout}");
assert!(output.stderr.is_empty(), "{cmd} {flag}: {}", String::from_utf8_lossy(&output.stderr));
}
}
}
#[test]
fn the_top_level_usage_still_lists_every_command() {
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).arg("--help").output().unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
for cmd in LISTED {
assert!(stdout.contains(&format!("solidcraft-cli {cmd}")), "{cmd}: {stdout}");
}
}
#[test]
fn fuzz_help_does_not_start_a_fuzz_run() {
let Some((code, stdout, stderr)) = run_within(Duration::from_secs(20), &["fuzz", "--help"]) else {
panic!("`fuzz --help` kept running: it started a fuzz run instead of printing the usage");
};
assert_eq!(code, Some(0), "{stderr}");
assert!(stdout.starts_with("usage:"), "{stdout}");
assert_eq!(mentioned(&stdout), vec!["fuzz"], "{stdout}");
assert!(stderr.is_empty(), "{stderr}");
}
#[test]
fn fuzz_refuses_an_argument_it_does_not_take() {
for arg in ["--not-a-flag", "stray"] {
let Some((code, _stdout, stderr)) = run_within(Duration::from_secs(20), &["fuzz", arg]) else {
panic!("`fuzz {arg}` kept running: it started a fuzz run instead of refusing the argument");
};
assert_ne!(code, Some(0), "`fuzz {arg}` succeeded");
assert!(stderr.contains(arg), "`fuzz {arg}`: {stderr}");
}
}
#[test]
fn render_help_keeps_the_registered_gpu_request_controls() {
for args in [vec!["--help"], vec!["render", "--help"]] {
let output = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args(&args).output().unwrap();
assert!(output.status.success(), "{args:?}: {}", String::from_utf8_lossy(&output.stderr));
assert!(output.stderr.is_empty());
let stdout = String::from_utf8_lossy(&output.stdout);
for flag in ["--progressive", "--samples", "--max-depth", "--seed", "--exposure", "--lighting", "--environment", "--tone-map"] {
assert!(stdout.contains(flag), "{args:?} lost {flag}: {stdout}");
}
}
}
+62
View File
@@ -89,3 +89,65 @@ fn mcp_connect_without_a_port_is_refused_at_start() {
assert!(stderr.contains("HOST:PORT"), "{addr}: {stderr}");
}
}
/// `mcp` with no arguments finds the running app through the discovery file and uses that
/// launch's token; with no app behind the file it stays a headless session.
#[test]
fn mcp_without_arguments_finds_the_running_app() {
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let port = listener.local_addr().unwrap().port();
let control_dir = std::env::temp_dir().join(format!("solidcraft-mcp-discover-{}-{port}", std::process::id()));
std::fs::create_dir_all(&control_dir).unwrap();
let token = "fedcba9876543210".repeat(4);
std::fs::write(control_dir.join(format!("{port}.token")), &token).unwrap();
std::fs::write(control_dir.join("current.port"), format!("{port}\n")).unwrap();
let app = std::thread::spawn(move || {
// The discovery probe connects and leaves without a request; the tool call follows.
let (stream, line) = loop {
let (stream, _) = listener.accept().unwrap();
let mut line = String::new();
BufReader::new(stream.try_clone().unwrap()).read_line(&mut line).unwrap();
if !line.is_empty() {
break (stream, line);
}
};
let req: Value = serde_json::from_str(&line).unwrap();
let ok = req["token"] == json!(token);
let reply = json!({"id": req["id"], "ok": ok, "result": {"bodies": [], "method": req["method"]}, "error": "unauthorized"});
writeln!(&stream, "{reply}").unwrap();
});
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
.args(["mcp", "--allow-any-path"])
.env("SOLIDCRAFT_CONTROL_DIR", &control_dir)
.env_remove("SOLIDCRAFT_CONTROL_TOKEN")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
let mut stdout = BufReader::new(child.stdout.take().unwrap());
writeln!(stdin, "{}", json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "measure", "arguments": {}}})).unwrap();
let mut line = String::new();
stdout.read_line(&mut line).unwrap();
let r: Value = serde_json::from_str(&line).unwrap_or_else(|e| panic!("{e}: {line:?}"));
assert_ne!(r["result"]["isError"], true, "{r}");
assert!(r["result"]["content"][0]["text"].as_str().unwrap_or_default().contains("engine.execute"), "{r}");
app.join().unwrap();
drop(stdin);
assert!(child.wait().unwrap().success());
// The file names a port nothing listens on (the app crashed): a headless session instead.
let closed = TcpListener::bind("127.0.0.1:0").unwrap();
let dead = closed.local_addr().unwrap().port();
drop(closed);
std::fs::write(control_dir.join("current.port"), format!("{dead}\n")).unwrap();
let out = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
.args(["mcp", "--allow-any-path"])
.env("SOLIDCRAFT_CONTROL_DIR", &control_dir)
.stdin(Stdio::null())
.output()
.unwrap();
assert!(!String::from_utf8_lossy(&out.stderr).contains("bridged to the running app"), "{}", String::from_utf8_lossy(&out.stderr));
std::fs::remove_dir_all(&control_dir).ok();
}
@@ -0,0 +1,9 @@
//! Explicit native GPU registration must not change bridged MCP ownership.
use std::process::Command;
#[test]
fn connected_application_rejects_headless_gpu_installation_before_connecting() {
let result = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).args(["mcp", "--render-gpu", "--connect", "127.0.0.1:1"]).output().unwrap();
assert!(!result.status.success());
let message = String::from_utf8_lossy(&result.stderr);
assert!(message.contains("connected application owns its own device"), "{message}");
}
+82 -1
View File
@@ -7,7 +7,7 @@ use std::sync::Arc;
use serde_json::{Value, json};
use solidcraft_engine::Session;
use solidcraft_engine::doc::{ModelBody, ModelState};
use solidcraft_engine::doc::{ModelBody, ModelState, SavedModel};
fn corpus() -> Vec<PathBuf> {
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus");
@@ -149,6 +149,87 @@ fn imported_assemblies_open_from_their_model_as_evaluated() {
check("imported hinge", s);
}
/// `built` saved with `saved`, a model of it that keeps only some states (as a file without room
/// for all of them does, #3220), opens as the design evaluated again: evaluating just the
/// features whose state the file left out, then after suppressing a feature at, before or after
/// each state it kept, after undoing that, and with the marker moved there and back.
fn check_thinned(name: &str, built: &Session, saved: &SavedModel) {
let design = built.design();
let bytes =
solidcraft_engine::io::write_design_container_with_cache(&design, solidcraft_engine::io::DesignCompression::Fast, None, Some(saved)).unwrap();
assert!(solidcraft_engine::io::read_design_geometry(&bytes).is_some(), "{name}: the file keeps the final state");
let path = temp(&name.replace(' ', "-"));
std::fs::write(&path, &bytes).unwrap();
let mut cached = open(&path);
let mut rebuilt = Session::new(solidcraft_engine::io::read_design(&bytes).unwrap());
let features = saved.records["features"].as_array().unwrap();
let unkept = features.iter().filter(|f| f["skipped"] == false && f.get("state").is_none()).count();
assert!(unkept > 0, "{name}: some states left out");
assert_eq!(cached.model.recomputed, unkept, "{name}: evaluated just the features whose state the file left out");
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: opened from the file's model");
let n = cached.doc.features.len();
let mut at: Vec<usize> = features
.iter()
.enumerate()
.filter(|(_, f)| f["skipped"] == false && f.get("state").is_some())
.flat_map(|(k, _)| [k.saturating_sub(1), k, k + 1])
.filter(|i| *i < n)
.collect();
at.dedup();
for i in at {
let edit = json!({"feature": cached.doc.features[i].id, "suppressed": true});
let (a, b) = (cached.execute("timeline.suppress", &edit), rebuilt.execute("timeline.suppress", &edit));
assert_eq!(a.is_ok(), b.is_ok(), "{name}: suppressing feature {i}");
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: feature {i} suppressed");
if a.is_ok() {
cached.execute("edit.undo", &json!({})).unwrap();
rebuilt.execute("edit.undo", &json!({})).unwrap();
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: suppressing feature {i} undone");
}
for marker in [json!({"position": i}), json!({})] {
cached.execute("timeline.rollback", &marker).unwrap();
rebuilt.execute("timeline.rollback", &marker).unwrap();
assert_eq!(model(name, &cached), model(name, &rebuilt), "{name}: the marker at {marker}, from feature {i}");
}
}
let _ = std::fs::remove_file(&path);
}
/// A model too big for its file keeps the final state, then the slowest features' states as fit
/// (#3220). Both the smallest such model (the final state, and states that add no body to it)
/// and one that keeps more open as the design evaluated again. The placed components' design has
/// a cut whose fingerprint reads where the bodies before it lie, which a state left out does not
/// say until it is evaluated.
#[test]
fn thinned_models_open_as_evaluated() {
let corpus = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus/components-arm.solidcraft");
for sample in ["Sample Plate", "Plastic Enclosure", "components-arm"] {
let built = match solidcraft_engine::sample::samples().into_iter().find(|s| s.name == sample) {
Some(s) => {
let mut built = Session::default();
for c in (s.script)()["commands"].as_array().unwrap() {
built.execute(c["command"].as_str().unwrap(), &c["params"]).unwrap();
}
built
}
None => open(&corpus),
};
let saved = built.model.saved(&built.design()).unwrap();
// Every body a megabyte (the records fit in the room left over): each budget keeps one more
// body's worth of states.
let body = 1 << 20;
let pieces: Vec<Vec<(usize, usize)>> = (0..saved.bodies.len()).map(|i| vec![(i, body)]).collect();
let thinned: Vec<SavedModel> =
(0..=saved.bodies.len()).filter_map(|n| saved.thinned(&pieces, n * body + body - 1).map(|(m, _, _)| m)).collect();
let kept = |m: &SavedModel| m.records["features"].as_array().unwrap().iter().filter(|f| f.get("state").is_some()).count();
let smallest = thinned.first().unwrap();
assert!(smallest.records["states"].as_array().unwrap().len() < saved.records["states"].as_array().unwrap().len(), "{sample}");
let more = thinned.iter().find(|m| kept(m) > kept(smallest) && kept(m) < built.doc.features.len()).unwrap();
check_thinned(&format!("{sample} smallest"), &built, smallest);
check_thinned(&format!("{sample} thinned"), &built, more);
}
}
/// Opened from its model, a design evaluates only from the first feature an edit changes: the
/// saved states before it are kept as they were read (the same states, not evaluated again).
#[test]
+250
View File
@@ -59,6 +59,84 @@ fn cli_loads_both_design_encodings_by_contents() {
}
}
/// #1369: a pending concept that did not land in the numbered release must not be silently
/// dropped on open/resave, in either encoding or before replacing an existing destination.
#[test]
fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
use solidcraft_engine::{Session, doc, io};
let dir = Scratch::new();
for version in [33, doc::format::FORMAT] {
let mut experimental = doc::Document::new("Experimental");
experimental.format = doc::format::format_string(version);
experimental.format_pending.push("experimental-card".into());
for (extension, bytes) in [("json", io::write_design(&experimental)), ("solidcraft", io::write_design_container(&experimental).unwrap())] {
let source = dir.0.join(format!("experimental-{version}.{extension}"));
std::fs::write(&source, &bytes).unwrap();
let mut session = Session::default();
session.execute("solid.box", &json!({"length": 2, "width": 3, "height": 4})).unwrap();
let original = session.doc.clone();
let original_path = session.path.clone();
let undo = session.undo.len();
let dirty = session.is_dirty();
let err = session.execute("doc.open", &json!({"path": source})).unwrap_err().to_string();
assert!(err.contains("experimental-card") && err.contains("update SolidCraft"), "{err}");
assert_eq!(session.doc, original);
assert_eq!(session.path, original_path);
assert_eq!(session.undo.len(), undo);
assert_eq!(session.is_dirty(), dirty);
for existing in [false, true] {
let destination = dir.0.join(format!("output-{version}-{extension}-{existing}.{extension}"));
let backup = io::backup_path(&destination);
if existing {
std::fs::write(&destination, b"existing design").unwrap();
std::fs::write(&backup, b"existing backup").unwrap();
}
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
assert_eq!(output.status.code(), Some(1));
let error = String::from_utf8_lossy(&output.stderr);
assert!(error.contains("experimental-card") && error.contains("update SolidCraft"), "{error}");
if existing {
assert_eq!(std::fs::read(&destination).unwrap(), b"existing design");
assert_eq!(std::fs::read(&backup).unwrap(), b"existing backup");
} else {
assert!(!destination.exists() && !backup.exists());
}
assert_eq!(std::fs::read(&source).unwrap(), bytes);
}
}
}
}
#[test]
fn known_pending_concepts_and_released_files_round_trip_with_inert_metadata() {
use solidcraft_engine::{doc, io};
let dir = Scratch::new();
let current = doc::Document::new("Supported");
let mut released = current.clone();
released.format = doc::format::format_string(33);
released.format_pending.clear();
let mut experimental = released.clone();
experimental.format_pending.push("reserved-body-names".into());
for (index, mut document) in [current, released, experimental].into_iter().enumerate() {
document.metadata.0.insert("com.example.tool".into(), json!({"format_pending": ["experimental-card"], "physical_card": {"density": 700}}));
for (extension, bytes) in [("json", io::write_design(&document)), ("solidcraft", io::write_design_container(&document).unwrap())] {
let source = dir.0.join(format!("supported-{index}.{extension}"));
let destination = dir.0.join(format!("resaved-{index}.{extension}"));
std::fs::write(&source, &bytes).unwrap();
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
let reopened = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
assert_eq!(reopened.metadata, document.metadata);
assert_eq!(reopened.name, document.name);
assert_eq!(reopened.features, document.features);
assert_eq!(std::fs::read(&source).unwrap(), bytes);
}
}
}
#[test]
fn results_still_save_and_export_the_script_once() {
let dir = Scratch::new();
@@ -209,3 +287,175 @@ fn timings_and_profile_report_each_command() {
assert!(!output.status.success());
assert!(String::from_utf8_lossy(&output.stderr).contains("--timings needs a script"));
}
fn render_setup_script(dir: &Scratch, job: Value) -> PathBuf {
let path = dir.0.join("render.json");
std::fs::write(
&path,
json!({"commands":[
{"command":"solid.box","params":{"length":10,"width":10,"height":10}},
{"command":"render.settings.set","params":{"name":"Portable setup","set":{"width":16,"height":16,"job":job}}}
]})
.to_string(),
)
.unwrap();
path
}
#[cfg(feature = "raytrace-cpu")]
#[test]
fn render_uses_typed_cpu_setups_from_scripts_and_saved_designs() {
for options in [json!({}), json!({"progressive":{"samples_per_pixel":3,"seed":81},"lights":[]})] {
let dir = Scratch::new();
let script = render_setup_script(&dir, json!({"method":"cpu_raytrace","options":options}));
let image = dir.0.join("script.png");
let result = json_output(
cli()
.arg("render")
.arg(&script)
.args(["--setup", "Portable setup", "--out"])
.arg(&image)
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-cpu")
.output()
.unwrap(),
);
assert_eq!(result["preflight"]["resolved"]["executor"], "cpu-raytrace");
assert_eq!(result["export"]["frame"]["executor"], "cpu-raytrace");
assert_eq!(result["preflight"]["request"]["integrator"], "raytrace");
assert_eq!(result["preflight"]["request"]["raytrace"].get("progressive"), options.get("progressive"));
let png = image::load_from_memory(&std::fs::read(&image).unwrap()).unwrap();
assert_eq!((png.width(), png.height()), (16, 16));
let saved = dir.0.join("portable.solidcraft");
json_output(cli().arg("run").arg(&script).arg("--save").arg(&saved).output().unwrap());
let original = std::fs::read(&saved).unwrap();
let overridden = dir.0.join("overridden.png");
let result = json_output(
cli()
.arg("render")
.arg(&saved)
.args(["--setup", "Portable setup", "--out"])
.arg(&overridden)
.args(["--width", "12", "--height", "8", "--exposure", "1.25"])
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-cpu")
.output()
.unwrap(),
);
assert_eq!(result["preflight"]["request"]["width"], 12);
assert_eq!(result["preflight"]["request"]["height"], 8);
assert_eq!(result["preflight"]["request"]["raytrace"]["exposure_ev"], 1.25);
assert_eq!(result["export"]["frame"]["executor"], "cpu-raytrace");
assert_eq!(result["preflight"]["request"]["raytrace"].get("progressive"), options.get("progressive"));
let png = image::load_from_memory(&std::fs::read(overridden).unwrap()).unwrap();
assert_eq!((png.width(), png.height()), (12, 8));
assert_eq!(std::fs::read(saved).unwrap(), original, "CLI overrides must not rewrite saved intent");
}
}
#[test]
fn render_preserves_typed_raster_and_legacy_runtime_controls() {
for job in [Value::Null, json!({"method":"raster","transparent_background":true})] {
let dir = Scratch::new();
let script = render_setup_script(&dir, job.clone());
let out = dir.0.join("raster.png");
let mut command = cli();
command
.arg("render")
.arg(&script)
.args(["--setup", "Portable setup", "--out"])
.arg(&out)
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-raster");
if job.is_null() {
command.arg("--transparent");
}
let result = json_output(command.output().unwrap());
assert_eq!(result["preflight"]["resolved"]["executor"], "cpu-product");
assert_eq!(result["export"]["frame"]["executor"], "cpu-product");
assert_eq!(result["preflight"]["request"]["transparent_background"], true);
assert!(std::fs::read(out).unwrap().starts_with(b"\x89PNG\r\n\x1a\n"));
}
}
#[test]
fn typed_render_rejects_explicit_runtime_flags_before_gpu_or_option_file_reads() {
let dir = Scratch::new();
let script = render_setup_script(&dir, json!({"method":"gpu_path","options":{"samples":64,"seed":0}}));
let out = dir.0.join("existing.png");
std::fs::write(&out, b"previous completed image").unwrap();
for flags in [
&["--progressive"][..],
&["--transparent"][..],
&["--samples", "64"][..],
&["--max-depth", "8"][..],
&["--seed", "0"][..],
&["--lighting", "deliberately-missing-lighting.json"][..],
&["--tone-map", "reinhard"][..],
&["--environment", "deliberately-missing-environment.json"][..],
] {
let output = cli()
.arg("render")
.arg(&script)
.args(["--setup", "Portable setup", "--out"])
.arg(&out)
.arg("--overwrite")
.args(flags)
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-conflicts")
.output()
.unwrap();
assert_eq!(output.status.code(), Some(1));
let error = String::from_utf8_lossy(&output.stderr);
assert!(error.contains(&format!("{} cannot accompany a typed saved render setup", flags[0])), "{error}");
assert!(output.stdout.is_empty());
assert_eq!(std::fs::read(&out).unwrap(), b"previous completed image");
}
}
#[test]
fn typed_gpu_render_selects_its_host_without_a_progressive_runtime_flag() {
// A deliberately invalid policy proves that dispatch reached the GPU host
// without depending on an adapter or accepting a CPU fallback. The legacy
// --progressive path must still reach that same host-selection boundary.
for job in [json!({"method":"gpu_path","options":{"samples":2}}), Value::Null] {
let dir = Scratch::new();
let script = render_setup_script(&dir, job.clone());
let out = dir.0.join("gpu.png");
let mut command = cli();
command.arg("render").arg(&script).args(["--setup", "Portable setup", "--out"]).arg(&out).env("SOLIDCRAFT_GPU", "not-a-gpu-policy");
if job.is_null() {
command.arg("--progressive");
}
let output = command.output().unwrap();
assert_eq!(output.status.code(), Some(1));
let error = String::from_utf8_lossy(&output.stderr);
assert!(error.contains("SOLIDCRAFT_GPU must be auto, hardware, software or cpu"), "{error}");
assert!(!out.exists());
}
}
#[test]
fn render_setup_overrides_obey_shared_validation_before_host_initialization() {
let dir = Scratch::new();
let script = render_setup_script(&dir, json!({"method":"cpu_raytrace","options":{"progressive":{"samples_per_pixel":64}}}));
let out = dir.0.join("invalid.png");
// 1024 * 1024 * 64 exceeds the unchanged default Compact camera-sample
// budget. Admission must name that envelope before initializing any host.
for (flags, message) in [
(&["--width", "1024", "--height", "1024"][..], "CPU Compact exceeds its 16777216 camera-sample admission"),
(&["--width", "0"][..], "render output must be at least"),
(&["--exposure", "NaN"][..], "render exposure must be finite"),
] {
let output = cli()
.arg("render")
.arg(&script)
.args(["--setup", "Portable setup", "--out"])
.arg(&out)
.args(flags)
.env("SOLIDCRAFT_GPU", "must-not-be-read-for-invalid-settings")
.output()
.unwrap();
assert_eq!(output.status.code(), Some(1));
let error = String::from_utf8_lossy(&output.stderr);
assert!(error.contains(message), "{error}");
assert!(!out.exists());
}
}
+4
View File
@@ -13,6 +13,10 @@ workspace = true
name = "solidcraft-web"
path = "src/main.rs"
[features]
default = []
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
[dependencies]
solidcraft-engine = { workspace = true }
solidcraft-ui-egui = { workspace = true }
+1 -1
View File
@@ -294,7 +294,7 @@ mod web {
let options = Object::new();
let _ = Reflect::set(&options, &"multiple".into(), &JsValue::FALSE);
let extensions = Array::new();
for e in [".solidcraft", ".solvecraft", ".json", ".step", ".stp", ".3mf", ".stl", ".obj", ".glb", ".gltf"] {
for e in [".solidcraft", ".solvecraft", ".json", ".step", ".stp", ".3mf", ".stl", ".obj", ".glb", ".gltf", ".fcstd", ".3dm"] {
extensions.push(&e.into());
}
let accept = Object::new();
+3 -2
View File
@@ -499,8 +499,9 @@ fn input_picker(files: &Rc<Files>, intent: Intent) {
let Some(doc) = web_sys::window().and_then(|w| w.document()) else { return };
let Ok(input) = doc.create_element("input").map(|e| e.unchecked_into::<web_sys::HtmlInputElement>()) else { return };
input.set_type("file");
// Everything the browser build imports, from the format registry (no cadmpeg formats here).
let accept = solidcraft_engine::io::formats::open_extensions(false).iter().map(|e| format!(".{e}")).collect::<Vec<_>>().join(",");
// Everything the browser build imports, from the format registry: cadmpeg is pure Rust, so
// FreeCAD and Rhino files are part of it.
let accept = solidcraft_engine::io::formats::open_extensions(true).iter().map(|e| format!(".{e}")).collect::<Vec<_>>().join(",");
input.set_accept(&accept);
// Attached (hidden) while it is open: some browsers ignore a click on a detached input.
let _ = input.set_attribute("style", "display:none");
+6
View File
@@ -131,6 +131,7 @@ mod web {
/// The language the fonts were last set up for (a CJK UI leads with the CJK face).
font_language: String,
space_mouse: SpaceMouse,
wheel_gestures: solidcraft_ui_egui::WebWheelGestures,
}
impl Shell {
@@ -509,6 +510,10 @@ mod web {
UNSAVED.with(|u| u.set(solidcraft_ui_egui::documents::any_dirty(&self.app)));
}
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
let now = raw.time.unwrap_or_else(|| ctx.input(|i| i.time));
if let Some(delay) = self.wheel_gestures.update(raw, now) {
ctx.request_repaint_after(delay);
}
files::take_drops(&self.files, raw);
// Before the app's own hook, which adds the control channel's events.
self.space_mouse.raw_input_hook(ctx, self.app.preferences.space_mouse_enabled, raw);
@@ -673,6 +678,7 @@ mod web {
cjk_bytes: None,
font_language,
space_mouse: SpaceMouse::default(),
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
}))
}),
)
+2 -2
View File
@@ -246,11 +246,11 @@ mod tests {
let axis =
|source, rest, full| solidcraft_ui_egui::space_mouse::AxisCalibration { source, reversed: false, rest, dead_zone: 0, full: [full; 2] };
// A short-range device: 15 of its 50 is under the raw dead zone, but 105 of 350 calibrated.
let short = Calibration { dead_zone_share: 0.0, axes: std::array::from_fn(|r| axis(r, 0, 50)) };
let short = Calibration { dead_zone_share: 0.0, kind: Default::default(), axes: std::array::from_fn(|r| axis(r, 0, 50)) };
let push = Axes { translation: [15, 0, 0], rotation: [0; 3] };
assert!(!push.moving() && moving(push, Some(short)));
// A device resting at 100 on every axis: past the raw dead zone, but at rest calibrated.
let offset = Calibration { dead_zone_share: 0.0, axes: std::array::from_fn(|r| axis(r, 100, 350)) };
let offset = Calibration { dead_zone_share: 0.0, kind: Default::default(), axes: std::array::from_fn(|r| axis(r, 100, 350)) };
let resting = Axes { translation: [100; 3], rotation: [100; 3] };
assert!(resting.moving() && !moving(resting, Some(offset)));
// Without a calibration, as read.
+7 -1
View File
@@ -7,6 +7,11 @@ license.workspace = true
publish = false
default-run = "solidcraft"
[features]
default = ["raytrace-cpu", "bundled-render-library"]
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
# The workspace lints, except unsafe_code: denied, not forbidden, so that src/open_documents.rs
# (macOS file-open events), src/native_crash.rs (Windows exception filter) and src/native_frame.rs
# (Windows resize border) can allow it: none has a safe API. Keep the rest in sync with [workspace.lints].
@@ -70,6 +75,7 @@ windows-sys = { version = "0.61", features = [
] }
[target.'cfg(target_os = "macos")'.dependencies]
muda = { version = "0.21.2", default-features = false }
# The 3D mouse through the 3Dconnexion driver, which keeps the device to itself (src/space_mouse.rs).
solidcraft-3dx-client = { workspace = true }
# The 3D mouse (src/space_mouse.rs), in the shared-device open mode so that a running 3DxWare
@@ -78,7 +84,7 @@ hidapi = { version = "2.6.7", default-features = false, features = ["macos-share
# Files opened from Finder and the Dock (src/open_documents.rs); all already come with eframe and rfd.
objc2 = "0.6"
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSResponder"] }
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "block2", "NSArray", "NSNotification", "NSOperation", "NSString", "NSURL"] }
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "block2", "NSArray", "NSNotification", "NSOperation", "NSProcessInfo", "NSString", "NSURL"] }
block2 = "0.6"
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
+97 -16
View File
@@ -11,6 +11,7 @@ use std::time::{Duration, Instant};
use serde_json::{Value, json};
use solidcraft_engine::MAX_LINE;
use solidcraft_engine::control_auth;
use solidcraft_engine::control_pending;
use solidcraft_ui_egui::ControlRequest;
/// Connections served at once, so a flood must not be able to spawn threads without end.
@@ -19,7 +20,7 @@ const MAX_CONNECTIONS: usize = 16;
const MAX_BAD_LINES: usize = 8;
static OPEN: AtomicUsize = AtomicUsize::new(0);
/// The token files this launch wrote (its `--control` port, the agent chat's endpoint), removed
/// The token files this launch wrote (its control port, the agent chat's endpoint), removed
/// when the app ends.
static TOKEN_FILES: std::sync::Mutex<Vec<std::path::PathBuf>> = std::sync::Mutex::new(Vec::new());
@@ -34,17 +35,45 @@ fn new_token() -> Option<String> {
pub fn remove_token_file() {
if let Ok(files) = TOKEN_FILES.lock() {
for p in files.iter() {
let _ = std::fs::remove_file(p);
control_auth::remove_token_file(p);
}
}
if let Some(port) = APP_PORT.lock().ok().and_then(|p| *p) {
control_auth::remove_current_port(port);
}
}
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
/// The port the app's own channel listens on, for removing the discovery file at exit.
static APP_PORT: std::sync::Mutex<Option<u16>> = std::sync::Mutex::new(None);
pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<ControlRequest> {
let (tx, rx) = channel::<ControlRequest>();
// Every connection must show the token before anything runs (docs/control-protocol.md).
match control_auth::env_token().or_else(new_token) {
Some(token) => {
listen(port, token, tx, ctx, false);
let listener = match listen_on {
control_auth::Listen::Exact(port) => TcpListener::bind(("127.0.0.1", port)),
control_auth::Listen::Default => control_auth::bind_with_fallback(control_auth::DEFAULT_PORT, control_auth::PORT_TRIES),
};
match listener {
Ok(l) => {
if let Some(port) = listen_on_listener(l, token, tx, ctx, false) {
if matches!(listen_on, control_auth::Listen::Default) {
eprintln!("solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)");
}
// The discovery file: where `solidcraft-cli mcp` finds this app with no arguments.
match control_auth::write_current_port(port) {
Ok(_) => {
if let Ok(mut p) = APP_PORT.lock() {
*p = Some(port);
}
}
Err(e) => eprintln!("solidcraft: could not write the control discovery file ({e})"),
}
}
}
Err(e) => eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
}
}
None => eprintln!("solidcraft: control server not started: no random token available"),
}
@@ -60,20 +89,20 @@ pub fn listen_agent(tx: Sender<ControlRequest>, ctx: egui::Context) -> Option<u1
eprintln!("solidcraft: agent chat endpoint not started: no random token available");
return None;
};
listen(0, token, tx, ctx, true)
}
/// Listen on `port` (0: a free port the system picks) with `token`; the port it listens on.
/// `patient`: every request may wait as long as a batch (a person decides on it).
fn listen(port: u16, token: String, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) -> Option<u16> {
let listener = match TcpListener::bind(("127.0.0.1", port)) {
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
Ok(l) => l,
Err(e) => {
eprintln!("solidcraft: control server failed to bind 127.0.0.1:{port}: {e}");
eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}");
return None;
}
};
let port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
listen_on_listener(listener, token, tx, ctx, true)
}
/// Serve `listener` (loopback) with `token`; the port it listens on.
/// `patient`: every request may wait as long as a batch (a person decides on it).
fn listen_on_listener(listener: TcpListener, token: String, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) -> Option<u16> {
let port = listener.local_addr().ok()?.port();
match control_auth::token_path(port) {
Some(path) => match control_auth::write_token_file(&path, &token) {
Ok(()) => {
@@ -176,6 +205,8 @@ const WAITS: Waits = Waits { short: Duration::from_secs(60), long: Duration::fro
struct Pending {
id: Value,
method: String,
/// The request in messages ("`engine.execute` (solid.fillet)").
what: String,
reply: Receiver<Value>,
}
@@ -296,6 +327,35 @@ fn watch(reply: Receiver<Value>, ticket: Ticket) -> Receiver<Value> {
late_rx
}
/// The reply text for a request that outlasts its wait. A request that only reads changes
/// nothing when it finishes; an export or a save still writes its file, and any other request
/// still applies, as it was not cancelled.
fn timeout_message(what: &str, effect: control_pending::Effect) -> String {
match effect {
control_pending::Effect::ReadsOnly => {
format!("timeout: {what} is still running in the app ({}); ask `control.pending` for its answer", effect.when_done())
}
control_pending::Effect::WritesFile => format!(
"timeout: {what} is still running in the app and will still write the file when it finishes (it was not cancelled: \
don't send it again; the design does not change); ask `control.pending` for its result"
),
control_pending::Effect::Changes => format!(
"timeout: {what} is still running in the app and will still apply when it finishes (it was not cancelled: don't send it \
again); ask `control.pending` for its result"
),
}
}
/// The reply text for a request refused because an earlier one on the connection still runs.
fn busy_message(p: &Pending, method: &str) -> String {
let method: String = method.chars().take(128).collect();
format!(
"busy: request {} ({}) is still running, so `{method}` was not run; ask `control.pending` for that request's result, then \
send this one again",
p.id, p.what
)
}
fn serve(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: egui::Context, patient: bool) {
serve_with(stream, token, tx, ctx, patient, WAITS, &RUNNING);
}
@@ -364,7 +424,7 @@ fn serve_with(
}
} else if let Some(p) = pending.as_ref().filter(|_| late.is_none()) {
// Nothing more is queued behind it, to run unseen when it ends.
json!({"ok": false, "busy": true, "error": format!("busy: request {} (`{}`) is still running; ask `control.pending` for its result", p.id, p.method)})
json!({"ok": false, "busy": true, "error": busy_message(p, &method)})
} else {
if late.is_some() {
// Finished, and the client moved on without asking: it is dropped.
@@ -372,6 +432,8 @@ fn serve_with(
}
// A batch replaces many short requests: the whole of it may take long (#743).
let wait = if patient || method == "engine.batch" { waits.long } else { waits.short };
let what = control_pending::describe(&method, &params);
let effect = control_pending::effect(&method, &params);
let (req, rrx) = ControlRequest::new(method.clone(), params);
let ticket = running.add(connection, &id, &req);
if tx.send(req).is_err() {
@@ -381,8 +443,8 @@ fn serve_with(
match rrx.recv_timeout(wait) {
Ok(r) => r,
Err(_) => {
let r = json!({"ok": false, "running": true, "error": format!("timeout: `{method}` is still running in the app and will still apply; ask `control.pending` for its result")});
pending = Some(Pending { id: id.clone(), method, reply: watch(rrx, ticket) });
let r = json!({"ok": false, "running": true, "error": timeout_message(&what, effect)});
pending = Some(Pending { id: id.clone(), method, what, reply: watch(rrx, ticket) });
r
}
}
@@ -577,6 +639,25 @@ mod tests {
assert_eq!((&r["result"]["pending"], &r["result"]["done"]), (&json!(7), &json!(true)), "{r}");
}
/// A timed-out request that only reads is not said to "still apply", nor is one that only
/// writes a file (an export, #1989); a change is, by name.
#[test]
fn timeout_text_tells_reads_from_changes() {
let says = |method: &str, p: Value| timeout_message(&control_pending::describe(method, &p), control_pending::effect(method, &p));
let read = says("ui.inspect", json!({}));
assert!(read.starts_with("timeout") && read.contains("only reads") && !read.contains("will still apply"), "{read}");
let read = says("engine.execute", json!({"command": "sketch.inspect"}));
assert!(read.contains("only reads") && read.contains("sketch.inspect"), "{read}");
let change = says("engine.execute", json!({"command": "solid.fillet"}));
assert!(change.starts_with("timeout") && change.contains("will still apply") && change.contains("solid.fillet"), "{change}");
let export = says("engine.execute", json!({"command": "file.export", "params": {"path": "/tmp/wall.step"}}));
assert!(export.contains("will still write the file") && !export.contains("apply") && export.contains("file.export"), "{export}");
let save = says("engine.execute", json!({"command": "file.save"}));
assert!(save.contains("will still write the file") && !save.contains("apply"), "{save}");
let render = says("ui.render", json!({"path": "/tmp/a.png"}));
assert!(render.contains("will still write the file"), "{render}");
}
/// #205: a connection runs nothing until a request carries the launch's token; a wrong or
/// missing token, or garbage, is refused and the connection closed. Once shown, the token
/// need not be repeated on that connection.
+110 -15
View File
@@ -8,8 +8,10 @@
//! A `.step`/`.stp` file opens as a new design holding the file's bodies (an Import feature); a
//! `.3mf`/`.stl`/`.obj`/`.glb`/`.gltf` file as a new design holding mesh bodies.
//!
//! `SOLIDCRAFT_SETTINGS_DIR` selects an explicit absolute settings directory without legacy adoption.
//! `--control <port>` (or `SOLIDCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control
//! server: `{"id":1,"method":"engine.execute","params":{"command":"solid.box","params":{…}}}`.
//! Without them the channel still opens, on the default port, unless Preferences turns it off.
//! See `solidcraft_ui_egui::control` and docs/control-protocol.md.
#![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")]
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
@@ -25,6 +27,8 @@ mod native_crash;
#[cfg(target_os = "windows")]
mod native_frame;
#[cfg(target_os = "macos")]
mod native_menu;
#[cfg(target_os = "macos")]
mod open_documents;
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
mod space_mouse;
@@ -81,6 +85,8 @@ struct App {
gpu: Option<LostWatch>,
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
space_mouse: space_mouse::SpaceMouse,
#[cfg(target_os = "macos")]
native_menu: Option<native_menu::NativeMenu>,
}
impl eframe::App for App {
@@ -123,6 +129,10 @@ impl eframe::App for App {
for f in if self.solve.file_picker_busy() { Vec::new() } else { open_documents::take() } {
self.solve.open_path_async(&f);
}
#[cfg(target_os = "macos")]
if let Some(menu) = &mut self.native_menu {
menu.tick(&mut self.solve, ctx);
}
self.solve.logic(ctx);
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
if !self.solve.file_picker_busy() {
@@ -132,10 +142,17 @@ impl eframe::App for App {
ctx.send_viewport_cmd(egui::ViewportCommand::Close);
}
}
fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) {
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
#[cfg(target_os = "macos")]
if let Some(menu) = &mut self.native_menu {
menu.raw_input_hook(&mut self.solve, ctx, raw);
}
// Before the app's own hook, which adds the control channel's events.
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
self.space_mouse.raw_input_hook(self.solve.preferences.space_mouse_enabled, raw);
// The 3D mouse's buttons, before the app reads the frame: Esc and held modifiers join its input.
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
solidcraft_ui_egui::space_mouse::buttons_hook(&mut self.solve, ctx, self.space_mouse.buttons(), raw);
self.solve.raw_input_hook(raw);
}
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
@@ -273,6 +290,7 @@ fn services(cc: &eframe::CreationContext<'_>) -> Services {
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
})),
graphics_path: graphics_path().map(|p| p.to_string_lossy().to_string()),
window_system: window_system_now(),
http_get: Some(std::sync::Arc::new(http_get)),
http_download: Some(std::sync::Arc::new(http_download)),
// Already an installed app: only the browser build offers to install.
@@ -442,14 +460,15 @@ fn http_call(url: &str, timeout: std::time::Duration) -> Result<ureq::http::Resp
}
/// The per-user settings folder: eframe's storage (`app.ron`) and the anti-aliasing preference.
fn settings_dir() -> Option<std::path::PathBuf> {
eframe::storage_dir("SolidCraft")
fn settings_dir() -> Result<Option<std::path::PathBuf>, String> {
let selected = std::env::var_os("SOLIDCRAFT_SETTINGS_DIR");
startup::settings_directory(selected.as_deref(), eframe::storage_dir("SolidCraft"))
}
/// The anti-aliasing preference, kept next to eframe's storage (it is needed before the
/// window opens).
fn graphics_path() -> Option<std::path::PathBuf> {
settings_dir().map(|d| d.join("graphics.json"))
settings_dir().ok().flatten().map(|d| d.join("graphics.json"))
}
/// Compatibility shim (docs/compatibility.md): on the first launch after the rename, copy the
@@ -459,7 +478,7 @@ fn graphics_path() -> Option<std::path::PathBuf> {
/// old app id's `~/.var/app` folder; that copy comes first, as it is the newest. The old folders
/// are never changed.
fn adopt_legacy_settings() {
let (Some(current), Some(legacy)) = (settings_dir(), eframe::storage_dir("SolveCraft")) else { return };
let (Some(current), Some(legacy)) = (settings_dir().ok().flatten(), eframe::storage_dir("SolveCraft")) else { return };
let legacy_eframe = eframe::storage_dir(solidcraft_engine::io::LEGACY_APP_ID);
let old_flatpak = solidcraft_engine::io::legacy_flatpak_path(&current);
if let Err(e) = adopt_settings(&current, old_flatpak.as_deref(), &legacy, legacy_eframe.as_deref()) {
@@ -556,8 +575,14 @@ fn app_viewport() -> Result<egui::ViewportBuilder, eframe::Error> {
const APP_ID: &str = solidcraft_engine::io::APP_ID;
fn main() -> eframe::Result {
#[cfg(target_os = "macos")]
objc2_foundation::NSProcessInfo::processInfo().setProcessName(objc2_foundation::ns_string!("SolidCraft"));
mark("main");
adopt_legacy_settings();
// Validate before legacy adoption, crash markers or any user preference read.
let settings = settings_dir().map_err(|error| eframe::Error::AppCreation(error.into()))?;
if std::env::var_os("SOLIDCRAFT_SETTINGS_DIR").is_none() {
adopt_legacy_settings();
}
let mut safe_mode = startup::env_on(std::env::var(startup::SAFE_MODE_ENV).ok().as_deref());
let mut control_port: Option<u16> = solidcraft_engine::geom::env::var("CONTROL_PORT").and_then(|p| p.parse().ok());
let mut files = Vec::new();
@@ -594,7 +619,6 @@ fn main() -> eframe::Result {
}
// Settings that don't read are moved aside; launches that never got a window are counted,
// and after a few in a row this one starts in safe mode (docs/book/src/guide/troubleshooting.md).
let settings = settings_dir();
let mut notes: Vec<String> = Vec::new();
let mut failed_starts = 0;
if let Some(dir) = &settings {
@@ -657,12 +681,24 @@ fn main() -> eframe::Result {
#[cfg(all(unix, not(target_os = "macos")))]
let mut options = options;
#[cfg(all(unix, not(target_os = "macos")))]
if prefer_x11(
std::env::var_os("DISPLAY").is_some(),
std::env::var_os("WAYLAND_DISPLAY").is_some(),
std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref(),
solidcraft_engine::geom::env::var("WAYLAND").as_deref(),
) {
let x11 = {
// SOLIDCRAFT_WAYLAND wins over Preferences › Graphics › Window system (#3083).
let environment = solidcraft_engine::geom::env::var("WAYLAND");
let preference = window_system_preference();
let wayland_override = environment.as_deref().or(window_system_override(preference.as_deref()));
let wayland_display = std::env::var_os("WAYLAND_DISPLAY").is_some();
let x11 = prefer_x11(
std::env::var_os("DISPLAY").is_some(),
wayland_display,
std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref(),
wayland_override,
);
// Without a Wayland socket winit opens on X11 anyway.
let _ = WINDOW_SYSTEM.set(if x11 || !wayland_display { "x11" } else { "wayland" });
x11
};
#[cfg(all(unix, not(target_os = "macos")))]
if x11 {
options.event_loop_builder = Some(Box::new(|b| {
use winit::platform::x11::EventLoopBuilderExtX11;
b.with_x11();
@@ -727,8 +763,10 @@ fn main() -> eframe::Result {
));
}
mark("viewport pipelines");
if let Some(port) = control_port {
let rx = control_server::start(port, cc.egui_ctx.clone());
// `--control`/the environment name a port; otherwise the preference (on by default)
// opens the default one (docs/control-protocol.md).
if let Some(listen_on) = solidcraft_engine::control_auth::plan(control_port, app.preferences.agent_control) {
let rx = control_server::start(listen_on, cc.egui_ctx.clone());
app = app.with_control(rx);
}
// The agent chat's MCP bridge reaches the app through an endpoint of its own, started
@@ -779,6 +817,18 @@ fn main() -> eframe::Result {
}
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
let space_mouse = space_mouse::SpaceMouse::start(&cc.egui_ctx);
#[cfg(target_os = "macos")]
let native_menu = match native_menu::NativeMenu::new(&app, &cc.egui_ctx) {
Ok(menu) => Some(menu),
Err(error) => {
app.set_status(format!("System menu: {error}"), true);
None
}
};
#[cfg(target_os = "macos")]
{
app.native_file_menu = native_menu.is_some();
}
mark("app created");
solidcraft_engine::crash::phase("first frame");
Ok(Box::new(App {
@@ -789,6 +839,8 @@ fn main() -> eframe::Result {
gpu,
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
space_mouse,
#[cfg(target_os = "macos")]
native_menu,
}))
}),
);
@@ -856,6 +908,36 @@ fn no_window(error: eframe::Error, tried_cpu: bool, settings: Option<&std::path:
Err(error)
}
/// Linux: the window system this run opened with (`wayland` or `x11`), for Preferences (#3083).
#[cfg(all(unix, not(target_os = "macos")))]
static WINDOW_SYSTEM: std::sync::OnceLock<&'static str> = std::sync::OnceLock::new();
/// The window system this run uses, where there is a choice (Linux).
fn window_system_now() -> Option<&'static str> {
#[cfg(all(unix, not(target_os = "macos")))]
return WINDOW_SYSTEM.get().copied();
#[cfg(not(all(unix, not(target_os = "macos"))))]
None
}
/// Preferences › Graphics › Window system (`graphics.json`'s `window_system`), read before the
/// window opens.
#[cfg(all(unix, not(target_os = "macos")))]
fn window_system_preference() -> Option<String> {
graphics().and_then(|v| v.get("window_system").and_then(serde_json::Value::as_str).map(str::to_string))
}
/// The window system preference as the `SOLIDCRAFT_WAYLAND` value it stands for: `wayland` is
/// `1`, `x11` is `0`, and `auto` (or anything else) leaves the choice to [`prefer_x11`].
#[cfg(all(unix, not(target_os = "macos")))]
fn window_system_override(preference: Option<&str>) -> Option<&'static str> {
match preference.map(str::trim) {
Some("wayland") => Some("1"),
Some("x11") => Some("0"),
_ => None,
}
}
/// Whether to run through XWayland rather than native Wayland. winit has no file drag-and-drop
/// on Wayland (only on X11), so dropping files from the file manager showed a "no" cursor: use
/// XWayland when it's there. Except on niri, whose XWayland (xwayland-satellite) breaks window
@@ -893,6 +975,19 @@ mod backend_tests {
assert!(prefer_x11(true, true, Some("niri"), Some("0")));
assert!(!prefer_x11(false, true, Some("niri"), Some("0")));
}
/// Preferences › Graphics › Window system stands for the same values (#3083).
#[test]
fn the_window_system_preference_maps_to_the_override() {
use super::window_system_override as over;
assert_eq!(
(over(Some("wayland")), over(Some("x11")), over(Some("auto")), over(None), over(Some("junk"))),
(Some("1"), Some("0"), None, None, None)
);
// Wayland chosen on GNOME keeps native Wayland; X11 chosen on niri goes through XWayland.
assert!(!prefer_x11(true, true, Some("GNOME"), over(Some("wayland"))));
assert!(prefer_x11(true, true, Some("niri"), over(Some("x11"))));
}
}
#[cfg(test)]
+189
View File
@@ -0,0 +1,189 @@
//! macOS system menus, owned and updated on eframe's main thread.
use std::sync::mpsc::{Receiver, sync_channel};
use muda::{
AboutMetadata, Menu, MenuEvent, MenuItem, PredefinedMenuItem, Submenu,
accelerator::{Accelerator, Code, Modifiers},
};
use solidcraft_ui_egui::{SolveApp, toolbar::FileAction};
pub struct NativeMenu {
menu: Menu,
actions: Vec<(MenuItem, FileAction)>,
help_actions: Vec<(MenuItem, &'static str)>,
events: Receiver<MenuEvent>,
recent: Submenu,
recent_paths: Vec<String>,
}
impl NativeMenu {
pub fn new(app: &SolveApp, ctx: &egui::Context) -> muda::Result<Self> {
let menu = Menu::new();
let application = Submenu::new("SolidCraft", true);
let file = Submenu::new(app.tr("ui.file"), true);
let edit = Submenu::new(app.tr_label("Edit"), true);
let recent = Submenu::new(app.tr("ui.open_recent"), true);
let window = Submenu::new("Window", true);
let help = Submenu::new(app.tr("ui.help"), true);
let mut help_actions = Vec::new();
let mut actions = Vec::new();
let mut add = |parent: &Submenu, label: &str, action: FileAction, shortcut: Option<Accelerator>| -> muda::Result<()> {
let item = MenuItem::new(label, Self::action_enabled(&action, app, ctx), shortcut);
parent.append(&item)?;
actions.push((item, action));
Ok(())
};
application.append(&PredefinedMenuItem::about(
Some("About SolidCraft"),
Some(AboutMetadata { name: Some("SolidCraft".into()), version: Some(env!("CARGO_PKG_VERSION").into()), ..Default::default() }),
))?;
application.append(&PredefinedMenuItem::separator())?;
add(
&application,
&format!("{}\u{2026}", app.tr("ui.preferences")),
FileAction::Settings,
Some(Accelerator::new(Modifiers::META, Code::Comma)),
)?;
add(&application, &format!("{}\u{2026}", app.tr("ui.keyboard_shortcuts")), FileAction::Shortcuts, None)?;
application.append(&PredefinedMenuItem::separator())?;
application.append(&PredefinedMenuItem::services(None))?;
application.append(&PredefinedMenuItem::separator())?;
application.append_items(&[
&PredefinedMenuItem::hide(Some("Hide SolidCraft")),
&PredefinedMenuItem::hide_others(None),
&PredefinedMenuItem::show_all(None),
&PredefinedMenuItem::separator(),
])?;
add(&application, &format!("{} SolidCraft", app.tr("ui.quit")), FileAction::Quit, Some(Accelerator::new(Modifiers::META, Code::KeyQ)))?;
add(&file, app.tr("ui.new_design"), FileAction::New, Some(Accelerator::new(Modifiers::META, Code::KeyN)))?;
add(&file, &format!("{}\u{2026}", app.tr("ui.open")), FileAction::Open, Some(Accelerator::new(Modifiers::META, Code::KeyO)))?;
file.append(&recent)?;
file.append(&PredefinedMenuItem::separator())?;
for (label, command) in
[("Insert STEP\u{2026}", "file.insert_step"), ("Insert Mesh\u{2026}", "file.insert_mesh"), ("Insert Design", "component.insert_design")]
{
add(&file, app.tr_label(label), FileAction::Command(command), None)?;
}
file.append(&PredefinedMenuItem::separator())?;
add(&file, app.tr("ui.save"), FileAction::Save, Some(Accelerator::new(Modifiers::META, Code::KeyS)))?;
add(
&file,
&format!("{}\u{2026}", app.tr("ui.save_as")),
FileAction::SaveAs,
Some(Accelerator::new(Modifiers::META | Modifiers::SHIFT, Code::KeyS)),
)?;
add(&file, app.tr_label("Save As JSON\u{2026}"), FileAction::SaveJson, None)?;
file.append(&PredefinedMenuItem::separator())?;
for (label, action) in [
("Export STL\u{2026}", FileAction::ExportMesh("stl")),
("Export 3MF\u{2026}", FileAction::ExportMesh("3mf")),
("Export STEP\u{2026}", FileAction::Export("step")),
("Export Current View as DXF\u{2026}", FileAction::Command("file.export_view_dxf")),
("Export OBJ\u{2026}", FileAction::Export("obj")),
("Export URDF\u{2026}", FileAction::Export("urdf")),
("Export Design as JSON\u{2026}", FileAction::Export("json")),
] {
add(&file, app.tr_label(label), action, None)?;
}
file.append(&PredefinedMenuItem::separator())?;
add(&file, app.tr_label("Design Properties"), FileAction::Command("document.metadata"), None)?;
add(&edit, app.tr("ui.undo"), FileAction::Undo, Some(Accelerator::new(Modifiers::META, Code::KeyZ)))?;
add(&edit, app.tr("ui.redo"), FileAction::Redo, Some(Accelerator::new(Modifiers::META | Modifiers::SHIFT, Code::KeyZ)))?;
add(&window, app.tr_label("Agent Chat"), FileAction::AgentChat, None)?;
window.append(&PredefinedMenuItem::separator())?;
window.append_items(&[
&PredefinedMenuItem::minimize(None),
&PredefinedMenuItem::maximize(None),
&PredefinedMenuItem::fullscreen(None),
&PredefinedMenuItem::separator(),
&PredefinedMenuItem::bring_all_to_front(None),
])?;
for (action, label) in [
("docs", "Documentation"),
("shortcuts", "Keyboard Shortcuts"),
("messages", "Messages\u{2026}"),
("report", "Report an Issue\u{2026}"),
("crashes", "Crash Reports\u{2026}"),
] {
let item = MenuItem::new(app.tr_label(label), true, None);
help.append(&item)?;
help_actions.push((item, action));
}
menu.append_items(&[&application, &file, &edit, &window, &help])?;
let (sender, events) = sync_channel(64);
let ctx = ctx.clone();
MenuEvent::set_event_handler(Some(move |event| {
let _ = sender.try_send(event);
ctx.request_repaint();
}));
menu.init_for_nsapp();
let mut native = Self { menu, actions, help_actions, events, recent, recent_paths: Vec::new() };
native.update_recent(app)?;
Ok(native)
}
pub fn raw_input_hook(&mut self, app: &mut SolveApp, ctx: &egui::Context, raw: &mut egui::RawInput) {
for event in self.events.try_iter().take(64) {
if let Some((_, action)) = self.help_actions.iter().find(|(item, _)| item.id() == &event.id) {
solidcraft_ui_egui::help::run(app, ctx, action);
}
if let Some((_, action)) = self.actions.iter().find(|(item, _)| item.id() == &event.id) {
if matches!(action, FileAction::Undo | FileAction::Redo) && ctx.egui_wants_keyboard_input() {
raw.events.push(egui::Event::Key {
key: egui::Key::Z,
physical_key: None,
pressed: true,
repeat: false,
modifiers: egui::Modifiers { command: true, mac_cmd: true, shift: matches!(action, FileAction::Redo), ..Default::default() },
});
} else {
action.run(app);
}
}
}
}
fn action_enabled(action: &FileAction, app: &SolveApp, ctx: &egui::Context) -> bool {
action.enabled(app)
|| (matches!(action, FileAction::Undo | FileAction::Redo) && ctx.egui_wants_keyboard_input() && FileAction::Settings.enabled(app))
}
pub fn tick(&mut self, app: &mut SolveApp, ctx: &egui::Context) {
for (item, action) in &self.actions {
let enabled = Self::action_enabled(action, app, ctx);
if item.is_enabled() != enabled {
item.set_enabled(enabled);
}
}
if !self.recent_paths.iter().eq(app.home.recent.iter().take(32))
&& let Err(error) = self.update_recent(app)
{
app.set_status(format!("Recent designs menu: {error}"), true);
}
}
fn update_recent(&mut self, app: &SolveApp) -> muda::Result<()> {
while self.recent.remove_at(0).is_some() {}
self.actions.retain(|(_, action)| !matches!(action, FileAction::OpenRecent(_) | FileAction::ClearRecent));
self.recent_paths = app.home.recent.iter().take(32).cloned().collect();
for path in &self.recent_paths {
let action = FileAction::OpenRecent(path.clone());
let item = MenuItem::new(path, action.enabled(app), None);
self.recent.append(&item)?;
self.actions.push((item, action));
}
self.recent.append(&PredefinedMenuItem::separator())?;
let clear = MenuItem::new(app.tr("ui.clear_recent"), !self.recent_paths.is_empty(), None);
self.recent.append(&clear)?;
self.actions.push((clear, FileAction::ClearRecent));
Ok(())
}
}
impl Drop for NativeMenu {
fn drop(&mut self) {
MenuEvent::set_event_handler(None::<fn(MenuEvent)>);
self.menu.remove_for_nsapp();
}
}
File diff suppressed because it is too large Load Diff
+44
View File
@@ -15,6 +15,12 @@ const DEV: &str = "/dev";
/// a read with nothing to read returns at once and the reader can see whether it should stop.
pub const O_NONBLOCK: i32 = 0o4000;
/// Opens a hidraw node the way the Direct reader reads it: read-only and non-blocking.
pub fn open(path: &Path) -> std::io::Result<std::fs::File> {
use std::os::unix::fs::OpenOptionsExt;
std::fs::OpenOptions::new().read(true).custom_flags(O_NONBLOCK).open(path)
}
/// How long the reader waits when the device has nothing to say, which is also the most a report
/// can be delayed.
const IDLE_POLL: Duration = Duration::from_millis(5);
@@ -76,6 +82,44 @@ fn ids(uevent: &str) -> Option<(u16, u16)> {
mod tests {
use super::*;
/// A real EACCES, as a user without the udev rule gets: a file of the user's own with no permission
/// bits. Skipped for root, which can open anything.
#[test]
fn a_device_without_permission_is_denied_and_the_daemon_takes_over_within_the_backoff() {
use std::os::unix::fs::{MetadataExt, PermissionsExt};
if std::fs::metadata("/proc/self").is_ok_and(|meta| meta.uid() == 0) {
return;
}
let dir = std::env::temp_dir().join(format!("sc-hidraw-denied-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let node = dir.join("hidraw0");
std::fs::write(&node, [0_u8; 4]).unwrap();
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o000)).unwrap();
let denied = open(&node);
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o600)).unwrap();
assert!(open(&node).is_ok(), "the same node opens once it is readable");
std::fs::remove_dir_all(&dir).ok();
assert_eq!(denied.err().map(|error| error.kind()), Some(std::io::ErrorKind::PermissionDenied));
// The failure is the choice's `since_failed`, with an injected clock: inside the backoff the
// daemon is read, whichever driver was asked for; after it the device is tried again.
use super::super::{Pick, SpaceMouseDriver, UNREADABLE_RETRY, choose, direct_usable};
for wanted in [SpaceMouseDriver::Automatic, SpaceMouseDriver::Spacenavd, SpaceMouseDriver::Direct] {
for ago in [Duration::ZERO, Duration::from_secs(29)] {
assert_eq!(choose(wanted, true, direct_usable(true, Some(ago), UNREADABLE_RETRY)), Some(Pick::Spacenavd), "{wanted:?} {ago:?}");
}
}
assert_eq!(choose(SpaceMouseDriver::Direct, true, direct_usable(true, Some(UNREADABLE_RETRY), UNREADABLE_RETRY)), Some(Pick::Direct));
// And the daemon's own reader delivers over a fake connection.
let (mut daemon, client) = std::os::unix::net::UnixStream::pair().unwrap();
let mut record = [0_u8; 32];
record[4..8].copy_from_slice(&100_i32.to_ne_bytes());
std::io::Write::write_all(&mut daemon, &record).unwrap();
drop(daemon);
let mut seen = 0;
super::super::spacenavd::read(client, |_| seen += 1, |_, _| {}, || false, Duration::from_secs(3600));
assert_eq!(seen, 1);
}
#[test]
fn reports_come_through_idle_gaps_until_the_device_ends_or_a_stop() {
use crate::space_mouse::fake::Script;
+90 -10
View File
@@ -3,7 +3,8 @@
//! to its clients, so a program that reads the HID device itself bypasses that setup. Over its
//! socket the daemon sends fixed 32-byte records: eight native-endian `i32`, the first the kind of
//! record (0 for motion, then x y z rx ry rz and the milliseconds since the last record).
//! Other records (button presses, from newer daemons also configuration changes) are ignored.
//! Button records (1 pressed, 2 released, then the button's number from 0) are read too; other
//! records (from newer daemons also configuration changes) are ignored.
//!
//! The socket is plain file I/O, so no library is needed, and it needs no udev rule: the daemon
//! is what has access to the device.
@@ -11,6 +12,7 @@
use std::io::Read;
use std::os::unix::net::UnixStream;
use std::path::Path;
use std::time::{Duration, Instant};
use solidcraft_ui_egui::space_mouse::{Axes, FULL_SCALE};
@@ -54,6 +56,22 @@ fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
})
}
/// The button a button record is about, from 0, and whether it went down; `None` for any other
/// record, or a button past the 32 a bitmask holds.
fn button(record: &[u8; RECORD_BYTES]) -> Option<(u32, bool)> {
let mut data = [0_i32; 2];
for (slot, bytes) in data.iter_mut().zip(record.as_chunks::<4>().0) {
*slot = i32::from_ne_bytes(*bytes);
}
let [kind, number] = data;
let number = u32::try_from(number).ok().filter(|&n| n < 32)?;
match kind {
1 => Some((number, true)),
2 => Some((number, false)),
_ => None,
}
}
/// A daemon value in our raw units. Out-of-range values stay in `i16`: they are input from another
/// process.
fn unit(value: impl Into<i64>) -> i16 {
@@ -61,14 +79,30 @@ fn unit(value: impl Into<i64>) -> i16 {
i16::try_from(scaled.clamp(i64::from(i16::MIN), i64::from(i16::MAX))).unwrap_or_default()
}
/// Hands every motion record to `publish` until the daemon closes the connection or fails, or
/// `stop` says so. A stream with a read timeout (`set_read_timeout`) times out when the daemon is
/// quiet, which is no record and no disconnect: `stop` is asked, and the read goes on, with what
/// of a record had arrived kept. A partial record at the end is dropped.
pub fn read(mut stream: impl Read, mut publish: impl FnMut(Axes), mut stop: impl FnMut() -> bool) {
/// Hands every motion record to `publish` and every button record to `pressed` until the daemon
/// closes the connection or fails, or `stop` says so. A stream with a read timeout
/// (`set_read_timeout`) times out when the daemon is quiet, which is no record and no disconnect:
/// `stop` is asked, and the read goes on, with what of a record had arrived kept. `stop` is also asked
/// at least every `check_every` while records keep arriving, so a daemon that never pauses for that
/// long (a moving cap sends every few milliseconds) does not keep the reader from noticing that the
/// device it prefers is back. A partial record at the end is dropped.
pub fn read(
mut stream: impl Read,
mut publish: impl FnMut(Axes),
mut pressed: impl FnMut(u32, bool),
mut stop: impl FnMut() -> bool,
check_every: Duration,
) {
let mut record = [0_u8; RECORD_BYTES];
let mut filled = 0;
let mut asked = Instant::now();
loop {
if asked.elapsed() >= check_every {
asked = Instant::now();
if stop() {
return;
}
}
match stream.read(record.get_mut(filled..).unwrap_or_default()) {
Ok(0) => {
log::info!("3D mouse: spacenavd connection ended");
@@ -80,11 +114,14 @@ pub fn read(mut stream: impl Read, mut publish: impl FnMut(Axes), mut stop: impl
filled = 0;
if let Some(axes) = decode(&record) {
publish(axes);
} else if let Some((number, down)) = button(&record) {
pressed(number, down);
}
}
}
Err(error) if error.kind() == std::io::ErrorKind::Interrupted => {}
Err(error) if matches!(error.kind(), std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut) => {
asked = Instant::now();
if stop() {
return;
}
@@ -134,6 +171,10 @@ mod tests {
fn other_records_and_hostile_values_are_harmless() {
// A button record (kind 1, press) and one of an unknown kind are not motion.
assert_eq!(decode(&record([1, 0, 1, 0, 0, 0, 0, 0])), None);
// Nor are hostile button numbers a button.
assert_eq!(button(&record([1, -1, 0, 0, 0, 0, 0, 0])), None);
assert_eq!(button(&record([1, i32::MAX, 0, 0, 0, 0, 0, 0])), None);
assert_eq!(button(&record([7, 3, 0, 0, 0, 0, 0, 0])), None);
assert_eq!(decode(&record([7, 500, 500, 500, 500, 500, 500, 0])), None);
// Nothing overflows on the extremes of `i32`.
let extreme = decode(&record([0, i32::MIN, i32::MIN, i32::MAX, i32::MAX, i32::MIN, i32::MIN, -1]));
@@ -147,14 +188,17 @@ mod tests {
let (mut daemon, client) = UnixStream::pair().expect("a socket pair");
let mut bytes = Vec::new();
bytes.extend(record([0, 100, 0, 0, 0, 0, 0, 8]));
bytes.extend(record([2, 1, 0, 0, 0, 0, 0, 0])); // a button release: skipped
bytes.extend(record([1, 4, 0, 0, 0, 0, 0, 0])); // button 4 down
bytes.extend(record([2, 4, 0, 0, 0, 0, 0, 0])); // and up
bytes.extend(record([1, 40, 0, 0, 0, 0, 0, 0])); // past the 32 a bitmask holds: skipped
bytes.extend(record([0, 0, 0, 0, 0, 0, 0, 900]));
bytes.extend([1, 2, 3]); // the daemon went away mid-record
daemon.write_all(&bytes).expect("written");
drop(daemon);
let mut seen = Vec::new();
read(client, |axes| seen.push(axes.translation), || false);
let (mut seen, mut buttons) = (Vec::new(), Vec::new());
read(client, |axes| seen.push(axes.translation), |number, down| buttons.push((number, down)), || false, Duration::from_secs(3600));
assert_eq!(seen, [[70, 0, 0], [0, 0, 0]]);
assert_eq!(buttons, [(4, true), (4, false)]);
}
#[test]
@@ -168,10 +212,12 @@ mod tests {
read(
script,
|axes| seen.push(axes.translation),
|_, _| {},
|| {
asked += 1;
false
},
Duration::from_secs(3600),
);
assert_eq!(seen, [[70, 0, 0]]);
assert_eq!(asked, 4, "each timeout asks whether to stop");
@@ -184,10 +230,44 @@ mod tests {
let mut script = Script::new();
script.data(&bytes).timeout(std::io::ErrorKind::WouldBlock).data(&bytes);
let mut seen = 0;
read(script, |_| seen += 1, || true);
read(script, |_| seen += 1, |_, _| {}, || true, Duration::from_secs(3600));
assert_eq!(seen, 1, "the record before the timeout arrived; the one after never was read");
}
/// A daemon that never goes quiet (a moving cap sends every few milliseconds) is still asked whether to
/// stop, at the interval given, so the reader can go back to a device that returned.
#[test]
fn a_busy_daemon_is_asked_whether_to_stop_on_a_timer() {
// Ends by itself after half a second, so a read that never asks fails the assertion below
// instead of hanging the test.
struct Busy(Instant);
impl Read for Busy {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.0.elapsed() > Duration::from_millis(500) {
return Ok(0);
}
std::thread::sleep(Duration::from_millis(2));
let bytes = record([0, 100, 0, 0, 0, 0, 0, 8]);
let size = bytes.len().min(buf.len());
buf[..size].copy_from_slice(&bytes[..size]);
Ok(size)
}
}
let (mut seen, mut asked) = (0, 0);
read(
Busy(Instant::now()),
|_| seen += 1,
|_, _| {},
|| {
asked += 1;
asked == 3
},
Duration::from_millis(10),
);
assert_eq!(asked, 3, "asked on the timer although no read ever timed out");
assert!(seen > 3, "records kept coming meanwhile: {seen}");
}
#[test]
fn no_daemon_means_no_connection() {
let missing = std::env::temp_dir().join(format!("sc-no-spnav-{}.sock", std::process::id()));
+32
View File
@@ -21,6 +21,25 @@ pub const RENDERER_ENV: &str = "SOLIDCRAFT_RENDERER";
pub const FAILED_STARTS_FOR_SAFE_MODE: u32 = 2;
const MARKER: &str = "starting";
/// Explicit application-settings isolation for side-by-side builds and native UI
/// acceptance. A supplied invalid path refuses startup rather than falling back
/// to the user's ordinary settings. No directory is read or created here.
pub fn settings_directory(override_path: Option<&std::ffi::OsStr>, default: Option<PathBuf>) -> Result<Option<PathBuf>, String> {
match override_path {
None => Ok(default),
Some(raw) => {
let path = PathBuf::from(raw);
if raw.is_empty() || !path.is_absolute() {
return Err("SOLIDCRAFT_SETTINGS_DIR must be a nonempty absolute directory path".into());
}
if path.exists() && !path.is_dir() {
return Err("SOLIDCRAFT_SETTINGS_DIR names a file, not a directory".into());
}
Ok(Some(path))
}
}
}
/// Whether an environment value switches something on.
pub fn env_on(v: Option<&str>) -> bool {
v.map(str::trim).is_some_and(|v| !v.is_empty() && v != "0" && !v.eq_ignore_ascii_case("false"))
@@ -214,6 +233,19 @@ mod tests {
d
}
#[test]
fn isolated_settings_are_explicit_absolute_and_never_fall_back() {
let default = std::env::temp_dir().join("ordinary-settings");
assert_eq!(settings_directory(None, Some(default.clone())).unwrap(), Some(default.clone()));
let selected = std::env::temp_dir().join("solidcraft-isolated-settings-fixture");
assert_eq!(settings_directory(Some(selected.as_os_str()), Some(default.clone())).unwrap(), Some(selected));
for raw in ["", "relative/settings", "../ordinary"] {
assert!(settings_directory(Some(std::ffi::OsStr::new(raw)), Some(default.clone())).is_err());
}
let file = std::env::current_exe().unwrap();
assert!(settings_directory(Some(file.as_os_str()), Some(default)).is_err());
}
#[test]
fn switches_read_from_the_environment() {
assert!(env_on(Some("1")) && env_on(Some("yes")) && !env_on(Some("0")) && !env_on(Some(" ")) && !env_on(None) && !env_on(Some("false")));
+11
View File
@@ -0,0 +1,11 @@
//! Original illustrative opaque finishes; not measured optical or engineering data.
//! (name, sRGB color, metal fraction, perceptual roughness). IOR is an illustrative 1.5.
//! Separate names preserve the scalar library's appearance and saved assignments.
pub const OPAQUE_FINISHES: [(&str, [u8; 3], f64, f64); 6] = [
("Steel - Satin PBR", [158, 164, 172], 1.0, 0.4),
("Aluminum - Satin PBR", [204, 208, 214], 1.0, 0.3),
("Copper - Clean PBR", [204, 122, 80], 1.0, 0.25),
("ABS - Molded PBR", [222, 222, 216], 0.0, 0.4),
("PLA - Smooth PBR", [110, 160, 222], 0.0, 0.35),
("Wood - Oak Uniform PBR Preview", [190, 146, 92], 0.0, 0.65),
];
+292
View File
@@ -0,0 +1,292 @@
//! Original starter visual data, MIT OR Apache-2.0. No external material library.
use crate::appearance_pack::{Category, GlassBoundary, GlassRecipe, OpaqueRecipe, TextureAxis, TextureRecipe, VisualPreset};
const WOOD: &str = "Uniform opaque preview only; grain resources/mapping are not applied";
const SOLAR: &str = "Uniform opaque preview only; no cell grid, busbars, electricity or efficiency claim";
const REGOLITH: &str = "Uniform opaque preview only; no granular displacement or measured lunar optical data";
const OPAQUE: &str = "Illustrative opaque isotropic PBR; no measured alloy, polymer or engineering properties";
const fn look(color: [u8; 3], metallic: f64, roughness: f64, dielectric_ior: f64) -> OpaqueRecipe {
OpaqueRecipe { color, metallic, roughness, dielectric_ior }
}
const fn maps(id: &'static str, repeat_mm: [f64; 2], grain: bool) -> Option<TextureRecipe> {
Some(TextureRecipe { id, repeat_mm, grain_axis: if grain { Some(TextureAxis::V) } else { None } })
}
const fn glass(transmission_linear_rgb: [f64; 3], ior: f64, reference_path_mm: f64, boundary: GlassBoundary) -> GlassRecipe {
GlassRecipe { transmission_linear_rgb, dielectric_ior: ior, reference_path_mm, boundary }
}
pub const STARTER_PACK: [VisualPreset; 40] = [
VisualPreset::opaque(
"starter.wood.oak-honey",
"Wood - Honey Oak Uniform Preview",
Category::Wood,
look([190, 145, 88], 0.0, 0.64, 1.5),
WOOD,
maps("starter.wood.oak-honey.maps-v1", [120.0, 240.0], true),
),
VisualPreset::opaque(
"starter.wood.oak-smoked",
"Wood - Smoked Oak Uniform Preview",
Category::Wood,
look([91, 72, 55], 0.0, 0.68, 1.5),
WOOD,
maps("starter.wood.oak-smoked.maps-v1", [120.0, 240.0], true),
),
VisualPreset::opaque(
"starter.wood.walnut-dark",
"Wood - Dark Walnut Uniform Preview",
Category::Wood,
look([99, 66, 43], 0.0, 0.60, 1.5),
WOOD,
maps("starter.wood.walnut-dark.maps-v1", [100.0, 220.0], true),
),
VisualPreset::opaque(
"starter.wood.maple-light",
"Wood - Light Maple Uniform Preview",
Category::Wood,
look([221, 192, 141], 0.0, 0.57, 1.5),
WOOD,
maps("starter.wood.maple-light.maps-v1", [100.0, 200.0], true),
),
VisualPreset::opaque(
"starter.wood.cherry-warm",
"Wood - Warm Cherry Uniform Preview",
Category::Wood,
look([164, 99, 65], 0.0, 0.58, 1.5),
WOOD,
maps("starter.wood.cherry-warm.maps-v1", [110.0, 220.0], true),
),
VisualPreset::opaque(
"starter.wood.ash-pale",
"Wood - Pale Ash Uniform Preview",
Category::Wood,
look([209, 186, 146], 0.0, 0.70, 1.5),
WOOD,
maps("starter.wood.ash-pale.maps-v1", [120.0, 260.0], true),
),
VisualPreset::opaque(
"starter.wood.birch-cream",
"Wood - Cream Birch Uniform Preview",
Category::Wood,
look([228, 206, 163], 0.0, 0.63, 1.5),
WOOD,
maps("starter.wood.birch-cream.maps-v1", [100.0, 200.0], true),
),
VisualPreset::opaque(
"starter.wood.teak-golden",
"Wood - Golden Teak Uniform Preview",
Category::Wood,
look([167, 122, 66], 0.0, 0.61, 1.5),
WOOD,
maps("starter.wood.teak-golden.maps-v1", [110.0, 250.0], true),
),
VisualPreset::opaque(
"starter.metal.carbon-steel-polished",
"Metal - Polished Carbon Steel",
Category::Metal,
look([161, 166, 173], 1.0, 0.16, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.stainless-soft-satin",
"Metal - Soft Satin Stainless",
Category::Metal,
look([187, 193, 201], 1.0, 0.36, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.aluminum-bead-blast",
"Metal - Bead Blast Aluminum",
Category::Metal,
look([195, 201, 210], 1.0, 0.58, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.aluminum-silver-polish",
"Metal - Silver Polish Aluminum",
Category::Metal,
look([218, 224, 232], 1.0, 0.13, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.copper-aged-warm",
"Metal - Warm Aged Copper",
Category::Metal,
look([171, 95, 57], 1.0, 0.48, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.brass-champagne",
"Metal - Champagne Satin Brass",
Category::Metal,
look([205, 173, 102], 1.0, 0.32, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.bronze-dark-cast",
"Metal - Dark Cast Bronze",
Category::Metal,
look([133, 95, 58], 1.0, 0.66, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.titanium-matte",
"Metal - Matte Gray Titanium",
Category::Metal,
look([147, 149, 157], 1.0, 0.53, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.gold-warm",
"Metal - Warm Reflective Gold",
Category::Metal,
look([235, 190, 82], 1.0, 0.18, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.metal.silver-cool",
"Metal - Cool Reflective Silver",
Category::Metal,
look([225, 230, 237], 1.0, 0.12, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.plastic.abs-charcoal",
"Plastic - Charcoal ABS",
Category::Plastic,
look([43, 45, 48], 0.0, 0.44, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.plastic.abs-warm-white",
"Plastic - Warm White ABS",
Category::Plastic,
look([232, 229, 220], 0.0, 0.40, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque("starter.plastic.pla-coral", "Plastic - Coral PLA", Category::Plastic, look([218, 99, 83], 0.0, 0.43, 1.5), OPAQUE, None),
VisualPreset::opaque(
"starter.plastic.petg-cyan-opaque",
"Plastic - Opaque Cyan PETG",
Category::Plastic,
look([36, 148, 167], 0.0, 0.29, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.plastic.nylon-ivory",
"Plastic - Ivory Nylon",
Category::Plastic,
look([220, 213, 190], 0.0, 0.62, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.plastic.pc-graphite-opaque",
"Plastic - Opaque Graphite PC",
Category::Plastic,
look([70, 76, 84], 0.0, 0.27, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.plastic.hdpe-natural-opaque",
"Plastic - Opaque Natural HDPE",
Category::Plastic,
look([221, 222, 207], 0.0, 0.48, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque("starter.plastic.pom-black", "Plastic - Black POM", Category::Plastic, look([29, 31, 35], 0.0, 0.32, 1.5), OPAQUE, None),
VisualPreset::opaque(
"starter.plastic.tpu-matte-black",
"Plastic - Matte Black TPU",
Category::Plastic,
look([31, 30, 32], 0.0, 0.83, 1.5),
OPAQUE,
None,
),
VisualPreset::opaque(
"starter.plastic.peek-amber-opaque",
"Plastic - Opaque Amber PEEK",
Category::Plastic,
look([157, 119, 62], 0.0, 0.51, 1.5),
OPAQUE,
None,
),
VisualPreset::glass(
"starter.glass.clear-soda-lime",
"Glass Recipe - Clear Soda Lime",
glass([0.92, 0.98, 0.96], 1.52, 3.0, GlassBoundary::Closed),
),
VisualPreset::glass("starter.glass.low-iron-clear", "Glass Recipe - Low Iron Clear", glass([0.98, 0.99, 1.0], 1.50, 4.0, GlassBoundary::Closed)),
VisualPreset::glass("starter.glass.smoky-neutral", "Glass Recipe - Smoky Neutral", glass([0.35, 0.38, 0.42], 1.52, 3.0, GlassBoundary::Closed)),
VisualPreset::glass("starter.glass.amber-tint", "Glass Recipe - Amber Tint", glass([0.84, 0.38, 0.08], 1.51, 3.0, GlassBoundary::Closed)),
VisualPreset::glass("starter.glass.cobalt-blue", "Glass Recipe - Cobalt Blue", glass([0.06, 0.14, 0.76], 1.53, 3.0, GlassBoundary::Closed)),
VisualPreset::glass(
"starter.glass.thin-clear",
"Glass Recipe - Thin Clear Sheet",
glass([0.95, 0.98, 0.99], 1.50, 2.0, GlassBoundary::Thin { thickness_mm: 2.0 }),
),
VisualPreset::opaque(
"starter.solar.mono-blue",
"Solar Panel - Mono Blue Uniform Preview",
Category::Solar,
look([20, 41, 85], 0.0, 0.20, 1.5),
SOLAR,
maps("starter.solar.mono-blue.maps-v1", [160.0, 160.0], false),
),
VisualPreset::opaque(
"starter.solar.thin-film-dark",
"Solar Panel - Dark Thin Film Uniform Preview",
Category::Solar,
look([28, 33, 42], 0.0, 0.28, 1.5),
SOLAR,
maps("starter.solar.thin-film-dark.maps-v1", [100.0, 200.0], false),
),
VisualPreset::opaque(
"starter.regolith.mare-charcoal",
"Regolith - Mare Charcoal Uniform Preview",
Category::Regolith,
look([56, 56, 55], 0.0, 0.97, 1.5),
REGOLITH,
maps("starter.regolith.mare-charcoal.maps-v1", [200.0, 200.0], false),
),
VisualPreset::opaque(
"starter.regolith.highland-pale",
"Regolith - Highland Pale Uniform Preview",
Category::Regolith,
look([116, 113, 106], 0.0, 0.93, 1.5),
REGOLITH,
maps("starter.regolith.highland-pale.maps-v1", [200.0, 200.0], false),
),
VisualPreset::opaque(
"starter.regolith.dust-neutral",
"Regolith - Neutral Dust Uniform Preview",
Category::Regolith,
look([86, 85, 81], 0.0, 1.0, 1.5),
REGOLITH,
maps("starter.regolith.dust-neutral.maps-v1", [150.0, 150.0], false),
),
VisualPreset::opaque(
"starter.regolith.agglutinate-dark",
"Regolith - Dark Agglutinate Uniform Preview",
Category::Regolith,
look([66, 63, 60], 0.0, 0.89, 1.5),
REGOLITH,
maps("starter.regolith.agglutinate-dark.maps-v1", [200.0, 200.0], false),
),
];
+3 -3
View File
@@ -78,7 +78,7 @@ Its projected circle uses the same axes; do not arbitrarily flatten the ellipse.
Only use the stable `source` identifiers below in model SVGs. The renderer maps roles to
light/dark theme values. `currentColor` is the background line-art role, not a way
to add arbitrary colours. All values and accents come from `design/palette.toml`.
to add arbitrary colours. Default display values and semantic accents come from `design/palette.toml`. Theme files may override those display roles and ramps through `icon_palette`; source identifiers and accent assignments stay unchanged.
| Role | Author helper | Light value | Use |
| --- | --- | --- | --- |
@@ -96,9 +96,9 @@ The source hexes identify paint roles, not the displayed colour. **Never recolou
SVG to apply a workspace accent.** Keep these identifiers and every finished shape
unchanged; the runtime and review page resolve them through `design/palette.toml`.
That file is the single colour authority: edit `light`/`dark` in the relevant
That file is the default colour authority: edit `light`/`dark` in the relevant
`accents` ramp; `active-3` is its primary stroke and `active-1/2` are shaded faces.
Do not edit a role's `source` identifier. `variants` holds colour-vision adjustments.
Do not edit a role's `source` identifier. Theme `icon_palette` overrides affect display colours only; colour-vision variants still take precedence. `variants` holds colour-vision adjustments.
A role with `use = "active-3"` follows that stroke automatically.
| Meaning | Accent | Assignment |
+1
View File
@@ -29,6 +29,7 @@ ground = "c_fix"
# Same finished drawing, different semantic operation. Colours live only in palette.toml.
sketch_break = { master = "break", accent = "sketch" }
sketch_cancel = { master = "close", accent = "sketch" }
sketch_dxf = { master = "dxf", accent = "sketch" }
sketch_move = { master = "move", accent = "sketch" }
sketch_scale = { master = "scale", accent = "sketch" }
@@ -104,6 +104,7 @@ flange false
flat false
fold false
folder false
frame_member false
gear false
graph false
ground false
@@ -211,6 +212,7 @@ shield false
silhouette false
sketch false
sketch_break false
sketch_cancel false
sketch_chamfer false
sketch_chamfer_angle false
sketch_chamfer_equal false
@@ -0,0 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<title>Frame Member</title>
<path d="M9.25 18.86L17.63 23.7" fill="none" stroke="#ffffff" stroke-width="4.4" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M9.25 18.86L17.63 23.7" fill="none" stroke="#236ead" stroke-width="2.2" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="9.25" cy="18.86" r="3" fill="#236ead"/>
<path d="M11.88 20.39L40.62 36.98L40.62 50.25L11.88 33.66Z" fill="#328bd3"/>
<path d="M23.38 13.75L52.12 30.34L40.62 36.98L11.88 20.39Z" fill="#b9ddf8"/>
<path d="M52.12 30.34L40.62 36.98L40.62 50.25L52.12 43.61ZM49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="#8e9faf" fill-rule="evenodd"/>
<defs><clipPath id="fm_hole"><path d="M49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="none"/></clipPath></defs>
<g clip-path="url(#fm_hole)">
<path d="M49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="#236ead"/>
<path d="M49.8 34.35L49.8 42.28L42.86 38.27L42.86 30.34Z" fill="#328bd3"/>
<path d="M49.8 42.28L42.94 46.24L35.99 42.23L42.86 38.27Z" fill="#b9ddf8"/>
</g>
<path d="M49.8 34.35L42.94 38.31L42.94 46.24L49.8 42.28Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M23.38 13.75L52.12 30.34L52.12 43.61L40.62 50.25L11.88 33.66L11.88 20.39Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M11.88 20.39L40.62 36.98L52.12 30.34" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M40.62 36.98L40.62 50.25" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M41.74 37.62L54.75 45.14" fill="none" stroke="#ffffff" stroke-width="4.4" stroke-linejoin="round" stroke-linecap="round"/>
<path d="M41.74 37.62L54.75 45.14" fill="none" stroke="#236ead" stroke-width="2.2" stroke-linejoin="round" stroke-linecap="round"/>
<circle cx="54.75" cy="45.14" r="3" fill="#236ead"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+36 -3
View File
@@ -1,4 +1,4 @@
//! Constraint, assembly snap and dimension marker artwork.
//! Constraint, assembly snap, hole placement and dimension marker artwork.
use crate::dialogs_assembly::SnapKind;
use crate::theme::Tokens;
use egui::{Color32, Pos2, Stroke, vec2};
@@ -57,6 +57,39 @@ pub(crate) fn snap(painter: &egui::Painter, c: Pos2, tip: Option<Pos2>, kind: Sn
painter.galley(at, g, t.text);
}
}
/// A place a hole can snap to, before the cursor reaches it: a small hollow mark of its kind
/// (a square for a face's centre, a ring for a round edge's centre).
pub(crate) fn snap_hint(painter: &egui::Painter, c: Pos2, kind: SnapKind, col: Color32) {
let ic = Tokens::get().icons;
for stroke in [Stroke::new(3.5, ic.marker_halo), Stroke::new(1.4, col)] {
match kind {
SnapKind::FaceCenter => {
painter.rect_stroke(egui::Rect::from_center_size(c, vec2(8.0, 8.0)), 1.0, stroke, egui::StrokeKind::Middle);
}
SnapKind::CircleCenter => {
painter.circle_stroke(c, 4.5, stroke);
}
SnapKind::EdgeMid | SnapKind::Vertex => {
let r = 5.0;
let diamond = vec![c + vec2(0.0, -r), c + vec2(r, 0.0), c + vec2(0.0, r), c + vec2(-r, 0.0)];
painter.add(egui::Shape::convex_polygon(diamond, Color32::TRANSPARENT, stroke));
}
}
}
painter.circle_filled(c, 1.3, col);
}
/// A placed hole's drag handle: a ring round the hole's centre with a cross through it, filled
/// while it is hovered or dragged.
pub(crate) fn hole_handle(painter: &egui::Painter, c: Pos2, col: Color32, hot: bool) {
let ic = Tokens::get().icons;
let r = 6.0;
painter.circle_filled(c, r + 3.5, ic.marker_halo);
painter.circle(c, r, if hot { col } else { col.gamma_multiply(0.35) }, Stroke::new(1.6, col));
let ink = Stroke::new(1.3, if hot { ic.highlight } else { col });
painter.line_segment([c - vec2(r + 3.0, 0.0), c + vec2(r + 3.0, 0.0)], ink);
painter.line_segment([c - vec2(0.0, r + 3.0), c + vec2(0.0, r + 3.0)], ink);
}
pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
Some(match name {
"Coincident" => "c_coincident",
@@ -75,8 +108,8 @@ pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
}
pub(crate) fn glyph_letter(name: &str) -> &'static str {
match name {
"Horizontal" => "H",
"Vertical" => "V",
"Horizontal" => "-",
"Vertical" => "|",
_ => "?",
}
}
+270 -54
View File
@@ -72,6 +72,7 @@
"Choose…": "Vybrat…",
"Circular": "Kruhově",
"Circumference": "Obvod",
"Clockwise": "Ve směru hodinových ručiček",
"Close draft": "Zavřít koncept",
"Collisions": "Kolize",
"Colour": "Barva",
@@ -322,6 +323,7 @@
"Search commands": "Hledat příkazy",
"Search this library": "Prohledat tuto knihovnu",
"Section": "Řez",
"Section position": "Poloha průřezu",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vybrat jiné: podržte levé tlačítko na modelu (nebo pravé tlačítko › Vybrat jiné…) a zobrazí se vše pod kurzorem; dalším kliknutím na stejné místo vyberete další prvek. Dvojklikem prostředním tlačítkem přejdete na výchozí pohled (Předvolby › Navigace).",
"Select bodies (a face picks its body)": "Vyberte tělesa (plocha vybere své těleso)",
"Select bodies or faces instead": "Vyberte místo toho tělesa nebo plochy",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft se zavřel dříve, než se tyto návrhy uložily. Lze otevřít jejich poslední automatické uložení.",
"Specialise": "Specializovat",
"Speed": "Rychlost",
"Square": "Čtverec",
"Stand it up": "Postavit",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Postavit: otočení o čtvrt otáčky kolem osy X jako jeden prvek Posunutí, který lze vrátit.",
"Stand this part up?": "Postavit tento díl?",
"Start angle": "Počáteční úhel",
"Start bulge": "Vypouklost začátku",
"Start continuity": "Návaznost začátku",
"Step": "Krok",
@@ -412,6 +416,7 @@
"angle, height": "úhel, výška",
"appearance.dielectric_ior": "Index lomu dielektrika",
"appearance.metallic": "Kovovost",
"appearance.only_available": "Pouze dostupné vzhledy",
"appearance.pbr_help": "Tento povrch zobrazíte v renderu nebo v produktovém PNG. Bez lomu a průchodu světla.",
"appearance.pbr_opaque": "Neprůhledné PBR",
"appearance.roughness": "Drsnost",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Vložit STEP",
"cmd.file_save_mesh": "Uložit jako síť",
"cmd.file_slicers": "Nainstalované slicery",
"cmd.frame_member": "Prvek rámu",
"cmd.frame_profile_insert": "Vložit profil",
"cmd.frame_profile_list": "Knihovna profilů",
"cmd.frame_profile_properties": "Vlastnosti profilu",
"cmd.inspect_accessibility": "Analýza přístupnosti",
"cmd.inspect_center_of_mass": "Těžiště",
"cmd.inspect_clearance": "Vůle",
@@ -569,6 +578,7 @@
"cmd.material_create": "Nový fyzický materiál",
"cmd.material_delete": "Smazat fyzický materiál",
"cmd.material_edit": "Upravit fyzický materiál",
"cmd.material_inspect": "Zkontrolovat fyzický materiál",
"cmd.material_list": "Seznam materiálů",
"cmd.material_properties_directional": "Směrový modul pružnosti",
"cmd.material_properties_validate": "Ověřit data fyzického materiálu",
@@ -615,6 +625,8 @@
"cmd.plastic_rules": "Seznam pravidel pro plasty",
"cmd.plastic_snap_fit": "Zacvakávací spoj",
"cmd.render_backends": "Renderovací jádra",
"cmd.render_camera_fit": "Zarámovat objekt renderu",
"cmd.render_camera_get": "Prohlédnout kameru renderu",
"cmd.render_cancel": "Zrušit render",
"cmd.render_export": "Exportovat obrázek renderu",
"cmd.render_preflight": "Zkontrolovat požadavek na render",
@@ -622,6 +634,7 @@
"cmd.render_settings_apply": "Použít kameru renderu",
"cmd.render_settings_get": "Nastavení renderu",
"cmd.render_settings_set": "Uložit nastavení renderu",
"cmd.render_stage_backdrop": "Uložit fyzické pozadí renderu",
"cmd.render_start": "Renderovat obrázek",
"cmd.render_status": "Průběh renderu",
"cmd.select_clear": "Zrušit výběr",
@@ -997,20 +1010,51 @@
"recovery.min_ago": "před {count} min",
"recovery.never_saved": "nikdy neuloženo",
"render.background": "Pozadí scény",
"render.bloom_enable": "Záře ve výstupu",
"render.bloom_help": "Kreativní záře z lineárních jasů se třemi normalizovanými průchody. Poloměr v pixelech výstupu, přičtení před sRGB. Nejde o simulaci čočky, objemu ani zlepšení konvergence. Surové EXR se nemění.",
"render.bloom_opaque": "Záře vyžaduje neprůhledný výstup. Průhledné PNG se odmítne, neprovede se skryté sloučení.",
"render.bloom_radius": "Poloměr záře",
"render.bloom_strength": "Síla záře",
"render.bloom_threshold": "Lineární práh jasů",
"render.cpu_aa_help": "Prostorové vzorky: více vzorků zlepší hrany a prodlouží vykreslení.",
"render.cpu_key_rim": "Hlavní a obrysové",
"render.cpu_lighting": "Předvolba osvětlení",
"render.cpu_neutral": "Neutrální",
"render.cpu_opaque_required": "CPU trasování vyžaduje neprůhledné pozadí. Vypněte průhledné pozadí.",
"render.cpu_options_pending": "Panel podporuje vzorky hran, předvolby světel, stíny a expozici. Další volby jsou v API.",
"render.cpu_primary_samples": "Vzorky hran na pixel",
"render.cpu_shadows": "Vrhat stíny",
"render.cpu_summary": "Neprůhledný CPU PNG · světla · stíny · bez skla a textur",
"render.cpu_warm_cool": "Teplé a studené",
"render.exr_pending": "GPU může uchovat surové lineární EXR. Komprese, vícevrstvé obrazy a pomocné průchody zatím nejsou dostupné.",
"render.fit": "Přizpůsobit produktu",
"render.gpu_depth": "Maximální hloubka cesty",
"render.gpu_options_help": "Tyto volby platí pro tuto úlohu. Expozice vychází z uloženého nastavení.",
"render.gpu_path_failed": "Trasování cest GPU není dostupné",
"render.gpu_path_first_setup": "První příprava na této GPU může trvat několik minut; SolidCraft lze mezitím používat. Při dalších spuštěních se použijí zkompilované shadery, pokud je ovladač uchová.",
"render.gpu_path_preparing": "Trasování cest GPU: kompilace shaderů…",
"render.gpu_seed": "Náhodné semeno",
"render.gpu_unavailable": "Trasování cest GPU není na tomto zařízení připraveno.",
"render.image_advanced": "Pokročilé nastavení GPU",
"render.image_cancelled": "Render zrušen",
"render.image_cancelling": "Rušení",
"render.image_completed": "Obrázek připraven k exportu",
"render.image_encoding": "Kódování PNG",
"render.image_export": "Exportovat PNG",
"render.image_export": "Exportovat hotový obrázek",
"render.image_exposure": "Expozice",
"render.image_failed": "Render selhal",
"render.image_height": "Výška",
"render.image_help": "Náhledový obrázek z CPU · PNG · přibližná průhlednost",
"render.image_host_missing": "Není dostupný vykreslovací hostitel.",
"render.image_method": "Metoda obrazu",
"render.image_overwrite": "Nahradit existující soubor",
"render.image_preparing": "Příprava obrázku",
"render.image_progressive": "Progresivní render",
"render.image_queued": "Čeká na render",
"render.image_reduce": "Zkuste menší obrázek nebo jednodušší scénu.",
"render.image_running": "Renderování obrázku",
"render.image_samples": "Vzorků na pixel",
"render.image_scene_stale": "Před kontrolou nebo spuštěním dokončete modelování nebo obnovte scénu.",
"render.image_stale": "Tento obrázek používá scénu zachycenou před pozdějšími změnami.",
"render.image_start": "Spustit render",
"render.image_supported": "Požadavek na render je podporován; fronta a paměť se znovu ověří při spuštění.",
@@ -1020,17 +1064,135 @@
"render.image_unknown": "Stav renderu není k dispozici",
"render.image_use_largest": "Použít největší velikost v tomto poměru stran:",
"render.image_width": "Šířka",
"render.material_absorption": "Absorpce R · G · B (na mm)",
"render.material_add": "Přidat nebo upravit těleso",
"render.material_assignments": "Přiřazení těles",
"render.material_binding_enable": "Zahrnout úpravy tohoto tělesa",
"render.material_box": "Osově zarovnaný kvádr",
"render.material_browse": "Vybrat obrázek…",
"render.material_certification": "Uzavřené sklo používá geometrickou vzdálenost. GPU příprava ověřuje topologii, orientaci, překryvy a optická rozhraní; kontrola jen přijímá požadavek.",
"render.material_choose_body": "Vybrat těleso scény",
"render.material_closed": "Uzavřená objemová síť",
"render.material_coat": "Čirý povlak na základním materiálu",
"render.material_coat_enable": "Zapnout čirý povlak",
"render.material_coat_help": "Ilustrativní dielektrický povlak na stávajícím neprůhledném vzhledu. Nulová váha ponechá základ beze změny.",
"render.material_color_map": "Obraz základní barvy (sRGB)",
"render.material_custom": "Vlastní ilustrativní optika",
"render.material_enable": "Použít tyto úpravy materiálů GPU",
"render.material_filter": "Filtrovat tělesa scény…",
"render.material_filter_help": "Zobrazuje se nejvýše 128 shod. U velkých scén zpřesněte filtr.",
"render.material_glass": "Sklo a optická absorpce",
"render.material_glass_enable": "Použít fyzikální sklo na těleso",
"render.material_glass_exclusive": "Tento renderer nepodporuje sklo s povlakem nebo mapami na témže tělese.",
"render.material_glass_help": "Původní ilustrativní recepty. Vzorky ukazují propustnost, ne render. Absorpce je −ln(lineární propustnosti) / referenční vzdálenost; alfa se nemění.",
"render.material_gpu_required": "Aktivní úpravy vyžadují GPU tracing. Před kontrolou nebo spuštěním vyberte GPU nebo úpravy vypněte.",
"render.material_ior": "Index lomu (IOR)",
"render.material_limits": "Nejvýše 128 nastavení těles, 32 oddělených skel, 64 povlaků a 128 vazeb textur. Shodné mapy sdílejí paměť; limity zařízení a celkové paměti zůstávají aktivní.",
"render.material_map_invalid": "Cesty map musí být omezené místní cesty; síla normál musí být konečná mezi 0 a 1.",
"render.material_map_required": "Před použitím map vyberte obraz barvy nebo normál.",
"render.material_maps": "Rovinné mapy barvy a normál",
"render.material_maps_enable": "Zapnout mapy neprůhledného tělesa",
"render.material_maps_help": "PNG/JPEG do 1024×1024; celkem 16 MiB kódovaných dat. U a V jsou kolmé vektory opakování ve světových mm (nejvýše 128 opakování). Barva je sRGB, normály OpenGL +Y. Soubory se zachytí při kontrole/spuštění.",
"render.material_normal_map": "Obraz normál (OpenGL, +Y)",
"render.material_normal_strength": "Síla normál (0–1)",
"render.material_origin": "Počátek mapování X · Y · Z (mm)",
"render.material_refresh": "Obnovit tělesa scény",
"render.material_refresh_needed": "Scéna se změnila. Před přidáním cíle obnovte seznam.",
"render.material_remove": "Odstranit přiřazení",
"render.material_repeat_u": "Jedno opakování U X · Y · Z (mm)",
"render.material_repeat_v": "Jedno opakování V X · Y · Z (mm)",
"render.material_revalidate": "Znovu ověřit těleso",
"render.material_revalidate_needed": "Přiřazení patří ke starší revizi scény. Před renderem znovu ověřte přesnou identitu tělesa.",
"render.material_roughness": "Drsnost povlaku (0,045–1)",
"render.material_shadow": "Propouštět přímé stínové paprsky",
"render.material_target_missing": "Pořadí a název tělesa již nesouhlasí. Odstraňte přiřazení a znovu výslovně vyberte těleso.",
"render.material_thickness": "Tloušťka vrstvy (mm)",
"render.material_thin": "Tenká vrstva",
"render.material_title": "Materiály GPU obrazu",
"render.material_transient": "Dočasné optické úpravy pro tuto kartu. Neukládají se do návrhu, nastavení renderu ani knihovny materiálů.",
"render.material_weight": "Váha povlaku (0–1)",
"render.method_cpu": "Trasování paprsků CPU",
"render.method_gpu": "Trasování cest GPU",
"render.method_raster": "CPU rastr",
"render.navigation_help": "Kamera se ovládá stejně jako v návrhu (Předvolby › Navigace). Uložit nastavení zaznamená aktuální kameru.",
"render.opaque_image_summary": "Náhledové PNG · neprůhledné PBR · smíšená průhlednost",
"render.opaque_preview_help": "Neprůhledné PBR a jednoduché barvy; smíšená průhlednost. Lom světla, HDRI a finální render zatím nejsou k dispozici.",
"render.output_format_status": "PNG je přesný dokončený obraz. Volitelné GPU EXR uchovává lineární FLOAT32 radianci před filtry. Export nikdy nenahrazuje výsledek náhledem nebo jinou úlohou.",
"render.perspective": "Perspektivní kamera",
"render.preview": "Integrovaný náhled",
"render.preview_help": "Rastrové stínování a smíšená průhlednost. Lom světla, HDRI a finální render zatím nejsou k dispozici.",
"render.progressive_image_summary": "Náhledové PNG · neprůhledné PBR · postupné vzorkování",
"render.reload_settings": "Znovu načíst uložené nastavení",
"render.restore_camera": "Obnovit uloženou kameru",
"render.result_actual_help": "Jeden pixel obrázku na jeden pixel obrazovky.",
"render.result_capture": "Zachycená revize",
"render.result_current": "Zachycená revize modelu je aktuální.",
"render.result_empty": "Zatím není hotový obrázek. Zkontrolujte nastavení a spusťte vykreslování.",
"render.result_fit": "Přizpůsobit obrázek",
"render.result_job": "Úloha",
"render.result_navigation": "Posun a přiblížení používají nastavení navigace. Mezerník + tažení posouvá.",
"render.result_open": "Otevřít hotový obrázek na hlavním plátně.",
"render.result_preview": "Živý náhled",
"render.result_retained": "Zobrazuje se poslední hotový obrázek; novější vykreslení jej zatím nenahradilo.",
"render.result_title": "Výsledek vykreslení",
"render.return_design": "Zpět k návrhu",
"render.ribbon_backgrounds": "Pozadí",
"render.ribbon_check": "Kontrola",
"render.ribbon_check_help": "Ověřit přesný aktuální návrh renderu. Bez skrytého přepnutí vykreslovače, zmenšení nebo nahrazení zdrojů.",
"render.ribbon_edit_lights": "Upravit světla",
"render.ribbon_environment_help": "HDR osvětlení, pozadí kamery a fyzické plochy jsou samostatné ovládací prvky s potvrzením nahrazení.",
"render.ribbon_finish_dialog": "Nejprve dokončete dialog souboru nebo potvrzení.",
"render.ribbon_frame": "Zarámovat",
"render.ribbon_hdr_library": "Knihovna HDR",
"render.ribbon_job_active": "Render již běží; před spuštěním dalšího jej výslovně zrušte.",
"render.ribbon_light_rigs": "Sestavy světel",
"render.ribbon_lights_help": "Zkontrolovat světelné sestavy nebo upravit podporovaná světla; aktuální nastavení nahradí až výslovné použití.",
"render.ribbon_materials": "Materiály",
"render.ribbon_materials_help": "Otevřít inspektor vzhledu a původní povrchy. Optické úpravy nemění skrytě technické vlastnosti.",
"render.ribbon_no_active_job": "Není aktivní render ke zrušení.",
"render.ribbon_no_result": "Před exportem dokončete render.",
"render.ribbon_no_saved_camera": "Před obnovením kamery uložte záběr.",
"render.ribbon_saved_camera": "Uložená kamera",
"render.ribbon_shots": "Záběry",
"render.ribbon_studios": "Studia",
"render.ribbon_studios_help": "Procházejte dostupná studia a před použitím zkontrolujte nahrazované prvky. Koncepty zůstávají označené.",
"render.ribbon_surface_maps": "Mapy povrchu",
"render.save_setup": "Uložit nastavení",
"render.setup_name": "Název nastavení",
"render.studio_add_area": "Přidat plošné světlo",
"render.studio_ambient": "Konstantní zář prostředí",
"render.studio_aperture": "Poloměr clony",
"render.studio_area": "Plošné světlo",
"render.studio_area_help": "Nejvýše čtyři jednostranné obdélníky. Kolmé poloviční hrany U a V určují velikost; U × V míří na osvětlenou stranu. Souřadnice jsou světové milimetry.",
"render.studio_center": "Střed X, Y, Z (mm)",
"render.studio_denoise": "Potlačit šum v hotovém obrazu",
"render.studio_denoise_help": "Závěrečný filtr respektuje hrany; může změkčit detaily. Sklo a ostré zrcadlové povrchy se nefiltrují.",
"render.studio_direction": "Směr ke slunci (X, Y, Z)",
"render.studio_environment": "Prostředí",
"render.studio_environment_bounds": "Intenzita prostředí musí být 0–100 a otočení −360° až 360°.",
"render.studio_environment_none": "Bez mapy prostředí",
"render.studio_focus": "Osová zaostřovací vzdálenost",
"render.studio_half_u": "Poloviční hrana U (mm)",
"render.studio_half_v": "Poloviční hrana V (mm)",
"render.studio_hdr": "Soubor Radiance HDR",
"render.studio_hdr_browse": "Vybrat HDR…",
"render.studio_hdr_help": "Radiance .hdr, panorama 2:1, nejvýše 2048 × 1024. EXR není dostupné. Předpoklad barev platí jen při neurčených primárních barvách.",
"render.studio_hdr_required": "Vyberte místní soubor .hdr.",
"render.studio_hdr_srgb": "Neurčené primární barvy chápat jako lineární sRGB",
"render.studio_intensity": "Intenzita prostředí",
"render.studio_lens": "Hloubka ostrosti",
"render.studio_lens_perspective": "Hloubka ostrosti vyžaduje perspektivní kameru.",
"render.studio_lighting": "Studiové osvětlení",
"render.studio_lighting_enable": "Použít vlastní studiové osvětlení",
"render.studio_radiance": "Lineární RGB zář",
"render.studio_remove_area": "Odebrat plošné světlo",
"render.studio_rotation": "Otočení prostředí",
"render.studio_runtime_help": "Optické úpravy těles jsou v Materiálech GPU obrazu. Ovládání studia a materiálů je dočasné a nemění uložené vzhledy.",
"render.studio_sky": "Umělecká obloha",
"render.studio_sky_help": "Původní přechod oblohy pro osvětlení; nejde o kalibrovaný model atmosféry.",
"render.studio_sun": "Slunce",
"render.studio_sun_radius": "Úhlový poloměr",
"render.studio_tone_map": "Mapování tónů",
"render.unavailable_preview": "Toto nastavení vyžaduje nedostupný profil náhledu.",
"render.unsaved": "Neuložené změny náhledu",
"render.use_preview": "Použít integrovaný náhled",
@@ -1116,12 +1278,115 @@
"toolbar.customize_note": "Karta {tab}. Zaškrtnutím příkazu přidáte jeho tlačítko na panel nástrojů. Můžete také přetáhnout řádek z rozbalovací nabídky panelu na panel nástrojů, odstranit tlačítko přetažením mimo panel nástrojů nebo použít připínáček na řádku rozbalovací nabídky.",
"toolbar.image": "OBRÁZEK",
"toolbar.render": "RENDER",
"toolbar.render_appearance": "VZHLED",
"toolbar.render_camera": "KAMERA",
"toolbar.render_environment": "PROSTŘEDÍ",
"toolbar.render_lighting": "OSVĚTLENÍ",
"toolbar.render_scene": "SCÉNA",
"trace.points": "Bodů podél cesty: {count}",
"trace.stopped_by_contact": "Končí ve vzorku {sample}: {a} by prošlo skrz {b}",
"trace.stopped_by_loop": "Končí ve vzorku {sample}: smyčku přes {joint} už nelze dále uzavřít",
"ui.1_degree_of_freedom": "1 stupeň volnosti",
"ui.3d_mouse_axes_reassigned": "Osy jsou v Předvolbách přeřazené a to platí i po této kalibraci.",
"ui.3d_mouse_button": "Tlačítko {number}",
"ui.3d_mouse_button_nothing": "Nic",
"ui.3d_mouse_button_settings": "Nastavení 3D myši",
"ui.3d_mouse_buttons": "Tlačítka",
"ui.3d_mouse_buttons_hint": "Stiskněte tlačítko na 3D myši, ať ho tu najdete, a vyberte, co má dělat.",
"ui.3d_mouse_buttons_no_device": "Připojte 3D myš a nastavte, co dělají její tlačítka.",
"ui.3d_mouse_buttons_reset": "Obnovit výchozí tlačítka",
"ui.3d_mouse_calibrate": "Kalibrovat…",
"ui.3d_mouse_calibrate_tip": "Pohybujte hlavicí podle pokynů v okně, aby se SolidCraft naučil, kterým směrem a jak daleko se pohybuje každá osa této 3D myši. Uloží se pro tuto 3D myš.",
"ui.3d_mouse_calibrate_title": "Kalibrace 3D myši",
"ui.3d_mouse_calibrate_wait": "Jednou pohněte 3D myší, aby ji SolidCraft uviděl.",
"ui.3d_mouse_calibrated": "Zkalibrováno",
"ui.3d_mouse_calibration": "Kalibrace 3D myši",
"ui.3d_mouse_calibration_done": "Hotovo. Uložte ji a použije se vždy, když bude tato 3D myš připojená.",
"ui.3d_mouse_calibration_no_response": "Jedna osa se skoro nepohnula, takže ji nelze zkalibrovat. Začněte znovu a pokaždé hlavicí pohněte dál.",
"ui.3d_mouse_circles_help": "Kruhy ukazují, co zařízení posílá po kalibraci. Zkušební model ukazuje, co s tím udělá pohled po obrácení, rychlosti a přiřazení os.",
"ui.3d_mouse_dead_zone": "Mrtvá zóna",
"ui.3d_mouse_dead_zone_tip": "Jak daleko se musí hlavice pohnout, než se pohne pohled, jako podíl jejího plného rozsahu. Zvyšte ji, pokud pohled ujíždí, i když je hlavice v klidu.",
"ui.3d_mouse_denied": "SolidCraft vidí 3D myš, ale nesmí ji číst. Pravidlo udev najdete v poznámkách k nastavení 3D myši.",
"ui.3d_mouse_denied_short": "Nalezeno, ale nelze číst",
"ui.3d_mouse_driven_by": "Který kalibrovaný pohyb zařízení ovládá tuto osu. Když vyberete pohyb, který už používá jiná osa, obě se prohodí.",
"ui.3d_mouse_driven_by_label": "ovládá",
"ui.3d_mouse_driver": "Ovladač 3D myši",
"ui.3d_mouse_driver_auto": "Automaticky",
"ui.3d_mouse_driver_direct": "Přímo (hidraw)",
"ui.3d_mouse_driver_fallback": "Zatím se čte přes spacenavd: zařízení samotné se nepodařilo otevřít.",
"ui.3d_mouse_driver_only_direct": "Zařízení se čte přímo; spacenavd neběží",
"ui.3d_mouse_driver_only_spacenavd": "Čte se přes spacenavd, jiná možnost není",
"ui.3d_mouse_forget": "Zapomenout kalibraci",
"ui.3d_mouse_ghost_left_viewport": "Vlevo ve výřezu",
"ui.3d_mouse_ghost_left_window": "Vlevo v okně",
"ui.3d_mouse_ghost_position": "Poloha",
"ui.3d_mouse_ghost_right_viewport": "Vpravo ve výřezu",
"ui.3d_mouse_hint": "Posun a otáčení pohledu současně; náčrty používají jen posun a přiblížení.",
"ui.3d_mouse_kind_cap": "Návrat do středu",
"ui.3d_mouse_kind_question": "Jaký typ 3D myši to je?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "Hodnota zůstává, dokud ji držíte, jako u zařízení typu „Návrat do středu“.",
"ui.3d_mouse_looks_like_trackball": "Hodnota se vrací zpět, jakmile přestanete hýbat, jako u trackballu.",
"ui.3d_mouse_motion_rotation_x": "Rotace X",
"ui.3d_mouse_motion_rotation_y": "Rotace Y",
"ui.3d_mouse_motion_rotation_z": "Rotace Z",
"ui.3d_mouse_motion_translation_x": "Posun X",
"ui.3d_mouse_motion_translation_y": "Posun Y",
"ui.3d_mouse_motion_translation_z": "Posun Z",
"ui.3d_mouse_navigate": "Navigace pomocí 3D myši",
"ui.3d_mouse_none": "3D myš nenalezena",
"ui.3d_mouse_not_calibrated": "Nezkalibrováno",
"ui.3d_mouse_offer": "Kalibrace 3D myši opraví prohozené nebo obrácené osy a nastaví její rychlost a pocit z ovládání.",
"ui.3d_mouse_offer_never": "Už nezobrazovat",
"ui.3d_mouse_offer_swap": "Přepnout na Hidraw (přímý ovladač)",
"ui.3d_mouse_reading": "Hodnoty",
"ui.3d_mouse_reset_axes": "Obnovit přiřazení os",
"ui.3d_mouse_reset_view": "Obnovit pohled",
"ui.3d_mouse_restore": "Obnovit výchozí nastavení 3D myši",
"ui.3d_mouse_restore_ask": "Vrátit nastavení 3D myši na výchozí hodnoty? Kalibrace a přiřazení tlačítek zůstanou.",
"ui.3d_mouse_section": "3D myš",
"ui.3d_mouse_show_in_view": "Zobrazit 3D myš ve 3D pohledu",
"ui.3d_mouse_show_in_view_tip": "Obrázek vaší 3D myši v levém dolním rohu, který se hýbe s ní.",
"ui.3d_mouse_skip": "Moje zařízení to neumí",
"ui.3d_mouse_skip_tip": "Přeskočí tento pohyb v obou směrech a kalibrace ho vynechá.",
"ui.3d_mouse_speed": "Rychlost 3D myši",
"ui.3d_mouse_step_lift": "Zvedněte hlavici nahoru, podržte a pusťte.",
"ui.3d_mouse_step_press": "Zatlačte hlavici dolů, podržte a pusťte.",
"ui.3d_mouse_step_pull": "Přitáhněte hlavici k sobě, podržte a pusťte.",
"ui.3d_mouse_step_push": "Odtlačte hlavici od sebe, podržte a pusťte.",
"ui.3d_mouse_step_rest": "Dejte ruku z hlavice a nechte ji v klidu.",
"ui.3d_mouse_step_rest_trackball": "Dejte ruku z kuličky a nechte ji v klidu.",
"ui.3d_mouse_step_ring_clockwise": "Otočte rolovacím kroužkem o pár cvaknutí po směru hodinových ručiček.",
"ui.3d_mouse_step_ring_counterclockwise": "Otočte rolovacím kroužkem o pár cvaknutí proti směru hodinových ručiček.",
"ui.3d_mouse_step_roll_away": "Několikrát kutálejte kuličkou od sebe, pak ji nechte zastavit.",
"ui.3d_mouse_step_roll_left": "Několikrát kutálejte kuličkou doleva, pak ji nechte zastavit.",
"ui.3d_mouse_step_roll_right": "Několikrát kutálejte kuličkou doprava, pak ji nechte zastavit.",
"ui.3d_mouse_step_roll_toward": "Několikrát kutálejte kuličkou k sobě, pak ji nechte zastavit.",
"ui.3d_mouse_step_slide_left": "Posuňte hlavici doleva, podržte a pusťte.",
"ui.3d_mouse_step_slide_right": "Posuňte hlavici doprava, podržte a pusťte.",
"ui.3d_mouse_step_spin_clockwise": "Několikrát roztočte kuličku po směru hodinových ručiček, pak ji nechte zastavit.",
"ui.3d_mouse_step_spin_counterclockwise": "Několikrát roztočte kuličku proti směru hodinových ručiček, pak ji nechte zastavit.",
"ui.3d_mouse_step_tilt_away": "Nakloňte vršek hlavice od sebe (okraj nejblíž vám jde nahoru), podržte a pusťte.",
"ui.3d_mouse_step_tilt_left": "Nakloňte hlavici doleva (levá hrana dolů), podržte a pusťte.",
"ui.3d_mouse_step_tilt_right": "Nakloňte hlavici doprava (pravá hrana dolů), podržte a pusťte.",
"ui.3d_mouse_step_tilt_toward": "Nakloňte vršek hlavice k sobě (okraj nejblíž vám jde dolů), podržte a pusťte.",
"ui.3d_mouse_step_touch": "Položte ruku na hlavici, ale netlačte na ni.",
"ui.3d_mouse_step_twist_clockwise": "Otočte hlavici po směru hodinových ručiček, podržte a pusťte.",
"ui.3d_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
"ui.3d_mouse_switch_cap": "Kalibrovat jako návrat do středu",
"ui.3d_mouse_switch_trackball": "Kalibrovat jako trackball",
"ui.3d_mouse_view_axis": "Osa pohledu",
"ui.3d_mouse_view_off": "Vypnuto",
"ui.3d_mouse_view_off_tip": "Profil ovladače 3D myši tuto osu vypíná, takže její pohyb s pohledem nehýbe. Když tu vyberete pohyb jiné osy, prohodí se s ní a s pohledem přestane hýbat.",
"ui.3d_mouse_view_orbit_sideways": "Orbit vlevo / vpravo",
"ui.3d_mouse_view_orbit_up_down": "Orbit nahoru / dolů",
"ui.3d_mouse_view_pan_sideways": "Posun vlevo / vpravo",
"ui.3d_mouse_view_pan_up_down": "Posun nahoru / dolů",
"ui.3d_mouse_view_roll": "Natočení",
"ui.3d_mouse_view_unmapped": "Osa {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "Čeká na pohyb",
"ui.3d_mouse_wiggle": "Pokud máte nějakou připojenou, zahýbejte s ní.",
"ui.3d_sketch": "3D náčrt",
"ui.activate": "Aktivovat",
"ui.active_component": "Aktivní komponenta",
@@ -1351,6 +1616,7 @@
"ui.blocked": "Blokováno",
"ui.bodies": "Tělesa",
"ui.bodies_all_if_none": "Tělesa (všechna, pokud žádné)",
"ui.bottom": "Zespodu",
"ui.box": "Kvádr",
"ui.break": "Přerušit",
"ui.browser_bodies_one": "{count} těleso",
@@ -1503,6 +1769,7 @@
"ui.grid_follows_view_hint": "Boční pohledy zobrazí mřížku na svislé rovině, která je k nim přivrácená, místo na zemi",
"ui.ground_shadow": "Stín na podložce",
"ui.groupSelected": "Seskupit vybrané",
"ui.hatch": "Šrafování",
"ui.heading_add_ins": "DOPLŇKY",
"ui.heading_assemble": "SESTAVA",
"ui.heading_box": "KVÁDR",
@@ -1576,6 +1843,8 @@
"ui.hideOrigin": "Skrýt počátek",
"ui.hidePlanes": "Skrýt roviny",
"ui.hideProfile": "Skrýt profil",
"ui.hide_origin_by_default": "Ve výchozím stavu skrýt počátek",
"ui.hide_origin_note": "Nové návrhy začínají se skrytou složkou Počátek. Návrh si ponechá vlastní nastavení počátku, jakmile ho zobrazíte nebo skryjete.",
"ui.history_back": "O krok zpět",
"ui.history_below_browser": "Pod prohlížečem",
"ui.history_first": "Přejít na začátek",
@@ -1793,59 +2062,6 @@
"ui.smooth_wheel_zoom": "Plynulé přiblížení kolečkem",
"ui.smooth_wheel_zoom_tip": "Vypnutím se každý pohyb kolečka projeví okamžitě, bez dobíhání",
"ui.snap": "Přichytávání",
"ui.space_mouse_axes_reassigned": "Osy jsou v Předvolbách přeřazené a to platí i po této kalibraci.",
"ui.space_mouse_calibrate": "Kalibrovat…",
"ui.space_mouse_calibrate_tip": "Pohybujte hlavicí podle pokynů v okně, aby se SolidCraft naučil, kterým směrem a jak daleko se pohybuje každá osa této 3D myši. Uloží se pro tuto 3D myš.",
"ui.space_mouse_calibrate_title": "Kalibrace 3D myši",
"ui.space_mouse_calibrated": "Zkalibrováno",
"ui.space_mouse_calibration": "Kalibrace 3D myši",
"ui.space_mouse_calibration_done": "Hotovo. Uložte ji a použije se vždy, když bude tato 3D myš připojená.",
"ui.space_mouse_calibration_no_response": "Jedna osa se skoro nepohnula, takže ji nelze zkalibrovat. Začněte znovu a pokaždé hlavicí pohněte dál.",
"ui.space_mouse_dead_zone": "Mrtvá zóna",
"ui.space_mouse_dead_zone_tip": "Jak daleko se musí hlavice pohnout, než se pohne pohled, jako podíl jejího plného rozsahu. Zvyšte ji, pokud pohled ujíždí, i když je hlavice v klidu.",
"ui.space_mouse_driven_by": "Který kalibrovaný pohyb zařízení ovládá tuto osu. Když vyberete pohyb, který už používá jiná osa, obě se prohodí.",
"ui.space_mouse_driven_by_label": "ovládá",
"ui.space_mouse_driver": "Ovladač 3D myši",
"ui.space_mouse_driver_auto": "Automaticky",
"ui.space_mouse_driver_direct": "Přímo (hidraw)",
"ui.space_mouse_driver_only_direct": "Zařízení se čte přímo; spacenavd neběží",
"ui.space_mouse_driver_only_spacenavd": "Čte se přes spacenavd, jiná možnost není",
"ui.space_mouse_forget": "Zapomenout kalibraci",
"ui.space_mouse_hint": "Posun a otáčení pohledu současně; náčrty používají jen posun a přiblížení.",
"ui.space_mouse_motion_rotation_x": "Rotace X",
"ui.space_mouse_motion_rotation_y": "Rotace Y",
"ui.space_mouse_motion_rotation_z": "Rotace Z",
"ui.space_mouse_motion_translation_x": "Posun X",
"ui.space_mouse_motion_translation_y": "Posun Y",
"ui.space_mouse_motion_translation_z": "Posun Z",
"ui.space_mouse_navigate": "Navigace pomocí SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "3D myš nenalezena",
"ui.space_mouse_not_calibrated": "Nezkalibrováno",
"ui.space_mouse_reading": "Hodnoty",
"ui.space_mouse_reset_axes": "Obnovit přiřazení os",
"ui.space_mouse_step_lift": "Zvedněte hlavici nahoru, podržte a pusťte.",
"ui.space_mouse_step_press": "Zatlačte hlavici dolů, podržte a pusťte.",
"ui.space_mouse_step_pull": "Přitáhněte hlavici k sobě, podržte a pusťte.",
"ui.space_mouse_step_push": "Odtlačte hlavici od sebe, podržte a pusťte.",
"ui.space_mouse_step_rest": "Dejte ruku z hlavice a nechte ji v klidu.",
"ui.space_mouse_step_slide_left": "Posuňte hlavici doleva, podržte a pusťte.",
"ui.space_mouse_step_slide_right": "Posuňte hlavici doprava, podržte a pusťte.",
"ui.space_mouse_step_tilt_back": "Nakloňte hlavici dozadu (přední hrana nahoru), podržte a pusťte.",
"ui.space_mouse_step_tilt_forward": "Nakloňte hlavici dopředu (přední hrana dolů), podržte a pusťte.",
"ui.space_mouse_step_tilt_left": "Nakloňte hlavici doleva (levá hrana dolů), podržte a pusťte.",
"ui.space_mouse_step_tilt_right": "Nakloňte hlavici doprava (pravá hrana dolů), podržte a pusťte.",
"ui.space_mouse_step_twist_clockwise": "Otočte hlavici po směru hodinových ručiček, podržte a pusťte.",
"ui.space_mouse_step_twist_counterclockwise": "Otočte hlavici proti směru hodinových ručiček, podržte a pusťte.",
"ui.space_mouse_view_axis": "Osa pohledu",
"ui.space_mouse_view_off": "Vypnuto",
"ui.space_mouse_view_off_tip": "Profil ovladače 3D myši tuto osu vypíná, takže její pohyb s pohledem nehýbe. Když tu vyberete pohyb jiné osy, prohodí se s ní a s pohledem přestane hýbat.",
"ui.space_mouse_view_orbit_sideways": "Orbit vlevo / vpravo",
"ui.space_mouse_view_orbit_up_down": "Orbit nahoru / dolů",
"ui.space_mouse_view_pan_sideways": "Posun vlevo / vpravo",
"ui.space_mouse_view_pan_up_down": "Posun nahoru / dolů",
"ui.space_mouse_view_roll": "Natočení",
"ui.space_mouse_view_unmapped": "Osa {n}",
"ui.space_mouse_view_zoom": "Zoom",
"ui.spacing": "Rozteč",
"ui.spacing_2": "Rozteč 2",
"ui.sphere": "Koule",
+111 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Vælg…",
"Circular": "Cirkulær",
"Circumference": "Omkreds",
"Clockwise": "Med uret",
"Close draft": "Luk kladde",
"Collisions": "Kollisioner",
"Colour": "Farve",
@@ -322,6 +323,7 @@
"Search commands": "Søg efter kommandoer",
"Search this library": "Søg i dette bibliotek",
"Section": "Snit",
"Section position": "Profilets placering",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vælg andet: Hold venstre museknap nede uden at flytte markøren på modellen (eller højreklik › Vælg andet…) for at vise alt under markøren. Klik igen samme sted for at vælge næste objekt. Dobbeltklik på den midterste museknap for at gå til Hjem (Indstillinger › Navigation).",
"Select bodies (a face picks its body)": "Vælg emner (en flade vælger sit emne)",
"Select bodies or faces instead": "Vælg emner eller flader i stedet",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft lukkede, før disse designs blev gemt. Den seneste automatiske lagring kan åbnes.",
"Specialise": "Specialisér",
"Speed": "Hastighed",
"Square": "Kvadrat",
"Stand it up": "Stil op",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Stil op: En kvart omdrejning om X som én flyttefeature, der kan fortrydes.",
"Stand this part up?": "Stil dette emne op?",
"Start angle": "Startvinkel",
"Start bulge": "Udbuling ved starten",
"Start continuity": "Overgang ved starten",
"Step": "Trin",
@@ -412,6 +416,7 @@
"angle, height": "vinkel, højde",
"appearance.dielectric_ior": "Dielektrisk brydningsindeks",
"appearance.metallic": "Metallisk",
"appearance.only_available": "Kun tilgængelige udseender",
"appearance.pbr_help": "Se denne overflade i Rendering eller i en billedfil. Lyset brydes ikke og passerer ikke gennem overfladen.",
"appearance.pbr_opaque": "Uigennemsigtig PBR",
"appearance.roughness": "Ruhed",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Indsæt STEP",
"cmd.file_save_mesh": "Gem som mesh",
"cmd.file_slicers": "Installerede slicere",
"cmd.frame_member": "Rammeelement",
"cmd.frame_profile_insert": "Indsæt profil",
"cmd.frame_profile_list": "Profilbibliotek",
"cmd.frame_profile_properties": "Profilegenskaber",
"cmd.inspect_accessibility": "Tilgængelighedsanalyse",
"cmd.inspect_center_of_mass": "Massecentrum",
"cmd.inspect_clearance": "Frigangskontrol",
@@ -569,6 +578,7 @@
"cmd.material_create": "Nyt fysisk materiale",
"cmd.material_delete": "Slet fysisk materiale",
"cmd.material_edit": "Redigér fysisk materiale",
"cmd.material_inspect": "Inspicér fysisk materiale",
"cmd.material_list": "Vis materialer",
"cmd.material_properties_directional": "Retningsafhængigt elasticitetsmodul",
"cmd.material_properties_validate": "Validér fysiske materialedata",
@@ -1121,7 +1131,106 @@
"trace.stopped_by_contact": "Slutter ved prøve {sample}: {a} ville gå gennem {b}",
"trace.stopped_by_loop": "Slutter ved prøve {sample}: Løkken gennem {joint} kan ikke lukkes yderligere",
"ui.1_degree_of_freedom": "1 frihedsgrad",
"ui.3d_mouse_axes_reassigned": "Akserne er omtildelt i Indstillinger, og det gælder stadig oven på denne kalibrering.",
"ui.3d_mouse_button": "Knap {number}",
"ui.3d_mouse_button_nothing": "Ingenting",
"ui.3d_mouse_button_settings": "Indstillinger for 3D-mus",
"ui.3d_mouse_buttons": "Knapper",
"ui.3d_mouse_buttons_hint": "Tryk på en knap på din 3D-mus for at finde den her, og vælg så, hvad den gør.",
"ui.3d_mouse_buttons_no_device": "Tilslut en 3D-mus for at vælge, hvad dens knapper gør.",
"ui.3d_mouse_buttons_reset": "Gendan standardknapper",
"ui.3d_mouse_calibrate": "Kalibrér…",
"ui.3d_mouse_calibrate_tip": "Bevæg knoppen som vist i vinduet, så SolidCraft kan registrere retningen og bevægelsesområdet for hver akse. Indstillingerne gemmes til denne 3D-mus.",
"ui.3d_mouse_calibrate_title": "Kalibrér 3D-mus",
"ui.3d_mouse_calibrate_wait": "Bevæg 3D-musen én gang, så SolidCraft kan se den.",
"ui.3d_mouse_calibrated": "Kalibreret",
"ui.3d_mouse_calibration": "Kalibrering af 3D-mus",
"ui.3d_mouse_calibration_done": "Færdig. Gem kalibreringen, så den bruges, hver gang denne 3D-mus tilsluttes.",
"ui.3d_mouse_calibration_no_response": "En af akserne bevægede sig for lidt til at kunne kalibreres. Start forfra, og bevæg knoppen længere hver gang.",
"ui.3d_mouse_circles_help": "Cirklerne viser, hvad enheden sender efter kalibrering. Testmodellen viser, hvad visningen gør med det efter omvending, hastighed og akseplacering.",
"ui.3d_mouse_dead_zone": "Dødzone",
"ui.3d_mouse_dead_zone_tip": "Hvor langt knoppen skal bevæges, før visningen reagerer, angivet som en andel af dens fulde bevægelse. Øg værdien, hvis visningen bevæger sig, når du ikke rører knoppen.",
"ui.3d_mouse_denied": "SolidCraft kan se en 3D-mus, men må ikke læse den. Se opsætningsnoterne til 3D-mus for udev-reglen.",
"ui.3d_mouse_denied_short": "Fundet, men kan ikke læses",
"ui.3d_mouse_driven_by": "Hvilken kalibreret bevægelse af enheden der styrer denne akse. Vælger du en bevægelse, som en anden akse allerede bruger, bytter de to plads.",
"ui.3d_mouse_driven_by_label": "styres af",
"ui.3d_mouse_driver": "3D-musedriver",
"ui.3d_mouse_driver_auto": "Automatisk",
"ui.3d_mouse_driver_direct": "Direkte (hidraw)",
"ui.3d_mouse_driver_fallback": "Læser indtil videre gennem spacenavd: selve enheden kunne ikke åbnes.",
"ui.3d_mouse_driver_only_direct": "Læser enheden direkte; spacenavd kører ikke",
"ui.3d_mouse_driver_only_spacenavd": "Læser gennem spacenavd, den eneste mulighed",
"ui.3d_mouse_forget": "Slet kalibrering",
"ui.3d_mouse_ghost_left_viewport": "Venstre visningsområde",
"ui.3d_mouse_ghost_left_window": "Venstre vindue",
"ui.3d_mouse_ghost_position": "Placering",
"ui.3d_mouse_ghost_right_viewport": "Højre visningsområde",
"ui.3d_mouse_hint": "Flyt og rotér visningen samtidigt; skitser bruger kun panorering og zoom.",
"ui.3d_mouse_kind_cap": "Vend tilbage til midten",
"ui.3d_mouse_kind_question": "Hvilken slags 3D-mus er det?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "Værdien bliver, mens du holder, som på en enhed af typen „Vend tilbage til midten“.",
"ui.3d_mouse_looks_like_trackball": "Værdien falder tilbage, så snart du stopper, som på en trackball.",
"ui.3d_mouse_motion_rotation_x": "Rotation X",
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
"ui.3d_mouse_motion_translation_x": "Forskydning X",
"ui.3d_mouse_motion_translation_y": "Forskydning Y",
"ui.3d_mouse_motion_translation_z": "Forskydning Z",
"ui.3d_mouse_navigate": "Navigér med 3D-musen",
"ui.3d_mouse_none": "Ingen 3D-mus fundet",
"ui.3d_mouse_not_calibrated": "Ikke kalibreret",
"ui.3d_mouse_offer": "Kalibrering af din 3D-mus retter ombyttede eller vendte akser og indstiller dens hastighed og fornemmelse.",
"ui.3d_mouse_offer_never": "Vis ikke igen",
"ui.3d_mouse_offer_swap": "Skift til Hidraw (direkte driver)",
"ui.3d_mouse_reading": "Aflæser",
"ui.3d_mouse_reset_axes": "Nulstil aksetildeling",
"ui.3d_mouse_reset_view": "Nulstil visning",
"ui.3d_mouse_restore": "Gendan 3D-musens standardværdier",
"ui.3d_mouse_restore_ask": "Sæt 3D-musens indstillinger tilbage til standard? Din kalibrering og knaptildeling bliver.",
"ui.3d_mouse_section": "3D-mus",
"ui.3d_mouse_show_in_view": "Vis 3D-musen i 3D-visningen",
"ui.3d_mouse_show_in_view_tip": "Et billede af din 3D-mus i nederste venstre hjørne, der bevæger sig, når du bevæger den.",
"ui.3d_mouse_skip": "Min enhed kan ikke det",
"ui.3d_mouse_skip_tip": "Springer bevægelsen over begge veje, og kalibreringen udelader den.",
"ui.3d_mouse_speed": "3D-musens hastighed",
"ui.3d_mouse_step_lift": "Løft knoppen op, hold den, og slip.",
"ui.3d_mouse_step_press": "Tryk knoppen ned, hold den, og slip.",
"ui.3d_mouse_step_pull": "Træk knoppen mod dig, hold den, og slip.",
"ui.3d_mouse_step_push": "Skub knoppen væk fra dig, hold den, og slip.",
"ui.3d_mouse_step_rest": "Fjern hånden fra knoppen, og lad den stå stille.",
"ui.3d_mouse_step_rest_trackball": "Fjern hånden fra kuglen, og lad den ligge stille.",
"ui.3d_mouse_step_ring_clockwise": "Drej rullehjulet et par klik med uret.",
"ui.3d_mouse_step_ring_counterclockwise": "Drej rullehjulet et par klik mod uret.",
"ui.3d_mouse_step_roll_away": "Rul kuglen væk fra dig et par gange, og lad den stoppe.",
"ui.3d_mouse_step_roll_left": "Rul kuglen mod venstre et par gange, og lad den stoppe.",
"ui.3d_mouse_step_roll_right": "Rul kuglen mod højre et par gange, og lad den stoppe.",
"ui.3d_mouse_step_roll_toward": "Rul kuglen ind mod dig et par gange, og lad den stoppe.",
"ui.3d_mouse_step_slide_left": "Skub knoppen mod venstre, hold den, og slip.",
"ui.3d_mouse_step_slide_right": "Skub knoppen mod højre, hold den, og slip.",
"ui.3d_mouse_step_spin_clockwise": "Drej kuglen rundt med uret et par gange, og lad den stoppe.",
"ui.3d_mouse_step_spin_counterclockwise": "Drej kuglen rundt mod uret et par gange, og lad den stoppe.",
"ui.3d_mouse_step_tilt_away": "Vip toppen af knoppen væk fra dig (kanten nærmest dig går op), hold den, og slip.",
"ui.3d_mouse_step_tilt_left": "Vip knoppen mod venstre (venstre kant ned), hold den, og slip.",
"ui.3d_mouse_step_tilt_right": "Vip knoppen mod højre (højre kant ned), hold den, og slip.",
"ui.3d_mouse_step_tilt_toward": "Vip toppen af knoppen ind mod dig (kanten nærmest dig går ned), hold den, og slip.",
"ui.3d_mouse_step_touch": "Hvil hånden på knoppen uden at skubbe til den.",
"ui.3d_mouse_step_twist_clockwise": "Drej knoppen med uret, hold den, og slip.",
"ui.3d_mouse_step_twist_counterclockwise": "Drej knoppen mod uret, hold den, og slip.",
"ui.3d_mouse_switch_cap": "Kalibrer som vend tilbage til midten",
"ui.3d_mouse_switch_trackball": "Kalibrér som trackball",
"ui.3d_mouse_view_axis": "Visningsakse",
"ui.3d_mouse_view_off": "Fra",
"ui.3d_mouse_view_off_tip": "3D-musedriverens profil slår denne akse fra, så dens bevægelse flytter ikke visningen. Vælger du en anden akses bevægelse her, bytter de to plads, og den bevægelse flytter ikke længere visningen.",
"ui.3d_mouse_view_orbit_sideways": "Orbit venstre / højre",
"ui.3d_mouse_view_orbit_up_down": "Orbit op / ned",
"ui.3d_mouse_view_pan_sideways": "Panorér venstre / højre",
"ui.3d_mouse_view_pan_up_down": "Panorér op / ned",
"ui.3d_mouse_view_roll": "Rul",
"ui.3d_mouse_view_unmapped": "Akse {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "Venter på bevægelse",
"ui.3d_mouse_wiggle": "Hvis du har en tilsluttet, så bevæg den lidt.",
"ui.3d_sketch": "3D-skitse",
"ui.activate": "Aktivér",
"ui.active_component": "Aktiv komponent",
@@ -1351,6 +1460,7 @@
"ui.blocked": "Blokeret",
"ui.bodies": "Emner",
"ui.bodies_all_if_none": "Emner (alle, hvis ingen er valgt)",
"ui.bottom": "Nedenfra",
"ui.box": "Boks",
"ui.break": "Bryd",
"ui.browser_bodies_one": "{count} emne",
@@ -1503,6 +1613,7 @@
"ui.grid_follows_view_hint": "Sidevisninger viser gitteret på det lodrette plan, der vender mod dem, i stedet for grundplanet",
"ui.ground_shadow": "Skygge på grundplan",
"ui.groupSelected": "Gruppér valgte",
"ui.hatch": "Skravering",
"ui.heading_add_ins": "ADD-INS",
"ui.heading_assemble": "SAMLING",
"ui.heading_box": "BOKS",
@@ -1793,34 +1904,6 @@
"ui.smooth_wheel_zoom": "Jævn zoom med musehjul",
"ui.smooth_wheel_zoom_tip": "Slå fra for at anvende hver hjulbevægelse straks uden efterfølgende udjævning",
"ui.snap": "Fastgør til geometri",
"ui.space_mouse_calibrate": "Kalibrér…",
"ui.space_mouse_calibrate_tip": "Bevæg knoppen som vist i vinduet, så SolidCraft kan registrere retningen og bevægelsesområdet for hver akse. Indstillingerne gemmes til denne 3D-mus.",
"ui.space_mouse_calibrate_title": "Kalibrér 3D-mus",
"ui.space_mouse_calibrated": "Kalibreret",
"ui.space_mouse_calibration": "Kalibrering af 3D-mus",
"ui.space_mouse_calibration_done": "Færdig. Gem indstillingerne, så de bruges, hver gang denne 3D-mus tilsluttes.",
"ui.space_mouse_calibration_no_response": "En af akserne bevægede sig for lidt til at kunne kalibreres. Start forfra, og bevæg knoppen længere hver gang.",
"ui.space_mouse_dead_zone": "Dødzone",
"ui.space_mouse_dead_zone_tip": "Hvor langt knoppen skal bevæges, før visningen reagerer, angivet som en andel af dens fulde bevægelse. Øg værdien, hvis visningen bevæger sig, når du ikke rører knoppen.",
"ui.space_mouse_forget": "Nulstil kalibrering",
"ui.space_mouse_hint": "Flyt og rotér visningen samtidigt; skitser bruger kun panorering og zoom.",
"ui.space_mouse_navigate": "Navigér med SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Ingen 3D-mus fundet",
"ui.space_mouse_not_calibrated": "Ikke kalibreret",
"ui.space_mouse_reading": "Aflæser",
"ui.space_mouse_step_lift": "Løft knoppen op, hold den dér, og slip.",
"ui.space_mouse_step_press": "Tryk knoppen ned, hold den dér, og slip.",
"ui.space_mouse_step_pull": "Træk knoppen mod dig, hold den dér, og slip.",
"ui.space_mouse_step_push": "Skub knoppen væk fra dig, hold den dér, og slip.",
"ui.space_mouse_step_rest": "Fjern hånden fra knoppen, og lad den stå stille.",
"ui.space_mouse_step_slide_left": "Skub knoppen mod venstre, hold den dér, og slip.",
"ui.space_mouse_step_slide_right": "Skub knoppen mod højre, hold den dér, og slip.",
"ui.space_mouse_step_tilt_back": "Vip knoppen bagud (forkanten op), hold den dér, og slip.",
"ui.space_mouse_step_tilt_forward": "Vip knoppen fremad (forkanten ned), hold den dér, og slip.",
"ui.space_mouse_step_tilt_left": "Vip knoppen mod venstre (venstre kant ned), hold den dér, og slip.",
"ui.space_mouse_step_tilt_right": "Vip knoppen mod højre (højre kant ned), hold den dér, og slip.",
"ui.space_mouse_step_twist_clockwise": "Drej knoppen med uret, hold den dér, og slip.",
"ui.space_mouse_step_twist_counterclockwise": "Drej knoppen mod uret, hold den dér, og slip.",
"ui.spacing": "Afstand",
"ui.spacing_2": "Afstand 2",
"ui.sphere": "Kugle",
+249 -55
View File
@@ -85,6 +85,7 @@
"Choose…": "Auswählen…",
"Circular": "Kreisförmig",
"Circumference": "Umfang",
"Clockwise": "Im Uhrzeigersinn",
"Close draft": "Entwurf schließen",
"Collapse": "Einklappen",
"Collisions": "Kollisionen",
@@ -404,6 +405,7 @@
"Search this library": "Diese Bibliothek durchsuchen",
"Second plane": "Zweite Ebene",
"Section": "Schnitt",
"Section position": "Lage des Querschnitts",
"Sections (one sketch each)": "Querschnitte (je eine Skizze)",
"Select Other": "Andere auswählen",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Anderes auswählen: Halten Sie die linke Taste ruhig auf dem Modell gedrückt (oder Rechtsklick › Anderes auswählen…), um alles unter dem Cursor aufzulisten; klicken Sie erneut an dieselbe Stelle, um das nächste Element dort auszuwählen. Doppelklick mit der mittleren Taste wechselt zur Startansicht (Einstellungen › Navigation).",
@@ -432,9 +434,11 @@
"Specialise": "Spezialisieren",
"Speed": "Geschwindigkeit",
"Splitting tool": "Trennwerkzeug",
"Square": "Quadrat",
"Stand it up": "Aufrichten",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Aufrichten: eine Vierteldrehung um X, als ein Verschieben-Merkmal, das sich rückgängig machen lässt.",
"Stand this part up?": "Dieses Teil aufrichten?",
"Start angle": "Startwinkel",
"Start bulge": "Startausbauchung",
"Start continuity": "Startstetigkeit",
"Step": "Schritt",
@@ -520,6 +524,7 @@
"angle, height": "Winkel, Höhe",
"appearance.dielectric_ior": "Brechungsindex des Dielektrikums",
"appearance.metallic": "Metallanteil",
"appearance.only_available": "Nur verfügbare Erscheinungsbilder",
"appearance.pbr_help": "Diese Oberfläche in Render oder einem Produkt-PNG ansehen. Keine Brechung oder Transmission.",
"appearance.pbr_opaque": "Opakes PBR",
"appearance.roughness": "Rauheit",
@@ -641,6 +646,10 @@
"cmd.file_insert_step": "STEP einfügen",
"cmd.file_save_mesh": "Als Netz speichern",
"cmd.file_slicers": "Installierte Slicer",
"cmd.frame_member": "Rahmenelement",
"cmd.frame_profile_insert": "Profil einfügen",
"cmd.frame_profile_list": "Profilbibliothek",
"cmd.frame_profile_properties": "Profileigenschaften",
"cmd.inspect_accessibility": "Zugänglichkeitsanalyse",
"cmd.inspect_center_of_mass": "Massenschwerpunkt",
"cmd.inspect_clearance": "Freiraum",
@@ -680,6 +689,7 @@
"cmd.material_create": "Neues physikalisches Material",
"cmd.material_delete": "Physisches Material löschen",
"cmd.material_edit": "Physisches Material bearbeiten",
"cmd.material_inspect": "Physikalisches Material prüfen",
"cmd.material_list": "Materialien auflisten",
"cmd.material_properties_directional": "Richtungsabhängiger Elastizitätsmodul",
"cmd.material_properties_validate": "Physikalische Materialdaten prüfen",
@@ -726,13 +736,32 @@
"cmd.plastic_rules": "Kunststoffregeln auflisten",
"cmd.plastic_snap_fit": "Rastverbindung",
"cmd.render_backends": "Render-Backends",
"cmd.render_camera_fit": "Renderobjekt einpassen",
"cmd.render_camera_get": "Renderkamera prüfen",
"cmd.render_cancel": "Rendering abbrechen",
"cmd.render_export": "PNG exportieren",
"cmd.render_hdr_inspect": "HDR-Quelle untersuchen",
"cmd.render_library_appearance_apply": "Opakes Bibliotheksaussehen anwenden",
"cmd.render_library_describe": "Render-Ressource untersuchen",
"cmd.render_library_environment_apply": "Integrierte HDR-Umgebung anwenden",
"cmd.render_library_list": "Render-Bibliothek durchsuchen",
"cmd.render_library_physical_prepare": "Prepare Physical Reference",
"cmd.render_library_studio_apply": "Erstellte Studiolichter anwenden",
"cmd.render_library_studio_prepare": "Erstellte Studiolichter vorbereiten",
"cmd.render_library_surface_bind": "Save Surface Binding",
"cmd.render_library_surface_prepare": "Erstellte Oberflächenmaps vorbereiten",
"cmd.render_library_surface_unbind": "Remove Surface Binding",
"cmd.render_library_targets": "Ziele für Render-Ressourcen suchen",
"cmd.render_preflight": "Renderanfrage prüfen",
"cmd.render_result_pin": "Angezeigtes Render-Ergebnis behalten",
"cmd.render_scene": "Renderszene prüfen",
"cmd.render_settings_apply": "Renderkamera anwenden",
"cmd.render_settings_capture_hdr": "Render-Einstellungen mit HDR speichern",
"cmd.render_settings_get": "Rendereinstellungen",
"cmd.render_settings_set": "Rendereinstellungen speichern",
"cmd.render_stage_backdrop": "Physischen Renderhintergrund speichern",
"cmd.render_stage_clear": "Remove Render Stage",
"cmd.render_stage_ground": "Save Render Ground",
"cmd.render_start": "Bild rendern",
"cmd.render_status": "Renderfortschritt",
"cmd.select_clear": "Auswahl aufheben",
@@ -1054,6 +1083,8 @@
"manage.material_hint": "Wählen Sie Körper und deren physikalisches Material aus.",
"manage.parameters_hint": "Bearbeiten Sie Ausdrücke und Einheiten. Änderungen werden mit der Eingabetaste oder beim Verlassen eines Feldes übernommen.",
"manage.rules_hint": "Wählen Sie eine Regel zum Bearbeiten oder erstellen Sie eine neue. Bestätigen Sie, um die Regel zu speichern.",
"material.editor.axes_numeric": "1 / 2 / 3",
"material.editor.axes_wood": "L / R / T",
"measure.at": "Bei",
"measure.item": "Element {number}",
"messages.ago_h": "vor {n} Std.",
@@ -1117,13 +1148,35 @@
"recovery.just_now": "gerade eben",
"recovery.min_ago": "vor {count} Min.",
"recovery.never_saved": "nie gespeichert",
"render.add_ground": "Add fitted ground to setup",
"render.background": "Szenenhintergrund",
"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.",
"render.bloom_radius": "Bloom-Radius",
"render.bloom_strength": "Bloom-Stärke",
"render.bloom_threshold": "Linearer Glanzlichtschwellwert",
"render.camera_pitch": "Kameraneigung",
"render.camera_set": "Render-Kamera einstellen",
"render.clear_stage": "Remove saved stage",
"render.cpu_budget": "CPU-Ressourcenprofil",
"render.cpu_budget_bodies": "Körperobergrenze",
"render.cpu_budget_compact": "Kompakt (bisherige Grenzen)",
"render.cpu_budget_details": "Ressourcengrenzen",
"render.cpu_budget_help": "Größere Grenzen ausdrücklich wählen. Auflösung und Samples werden nie automatisch reduziert.",
"render.cpu_budget_memory": "Speicherobergrenze pro Auftrag",
"render.cpu_budget_not_guarantee": "Dies sind Zulassungsgrenzen, keine RAM-Reservierung oder Geschwindigkeitsgarantie. Geometrie, Film und Vorschauen teilen das Speicherbudget. Das Rendern bleibt abbrechbar.",
"render.cpu_budget_resize": "Der Entwurf überschreitet das CPU-Profil oder sein Kamera-Sample-Budget. Kleinere Abmessungen ausdrücklich bestätigen oder ein größeres Profil wählen.",
"render.cpu_budget_studio": "Studio (große Bilder)",
"render.cpu_budget_triangles": "Dreiecksobergrenze",
"render.cpu_budget_workstation": "Workstation (komplexe Szenen)",
"render.exr_pending": "Die GPU kann rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
"render.fit": "Produkt einpassen",
"render.image_cancelled": "Rendering abgebrochen",
"render.image_cancelling": "Wird abgebrochen",
"render.image_completed": "Bild bereit zum Export",
"render.image_encoding": "PNG wird kodiert",
"render.image_export": "PNG exportieren",
"render.image_export": "Fertiges Bild exportieren",
"render.image_failed": "Rendering fehlgeschlagen",
"render.image_height": "Höhe",
"render.image_help": "CPU-Vorschaubild · PNG · angenäherte Transparenz",
@@ -1141,17 +1194,98 @@
"render.image_unknown": "Renderstatus nicht verfügbar",
"render.image_use_largest": "Größte Größe mit diesem Seitenverhältnis verwenden:",
"render.image_width": "Breite",
"render.include_rig_cards": "Include passive cards and flags",
"render.library_applied": "Ressourcenvariante angewendet. Vollständigen Renderauftrag vor dem Start prüfen.",
"render.library_apply_hdr": "Mit dieser HDR-Umgebung speichern",
"render.library_apply_maps": "Erstellte GPU-Oberflächenmaps verwenden",
"render.library_apply_studio": "Erstellte Studiovariante speichern",
"render.library_apply_uniform": "Einheitliches opakes Aussehen anwenden",
"render.library_authored_help": "Original erstellte Ressourcen dienen der Darstellung, nicht als gemessene technische Daten. Jede Aktion wendet nur ihre angegebene Variante an.",
"render.library_blocked": "Diese Ressource benötigt noch Kalibrierung oder eine externe Sonne und kann noch nicht angewendet werden.",
"render.library_center": "Mittelpunkt des Lichtaufbaus",
"render.library_emitters_only_ack": "Nur Strahler verwenden; dokumentierte passive Karten auslassen",
"render.library_environment_concepts": "Umgebungskonzepte",
"render.library_extent": "Objektausdehnung",
"render.library_hdr": "Integrierte HDR-Umgebungen",
"render.library_hdr_help": "Bettet das originale HDR in die gespeicherte Einstellung ein. Der Längengradursprung wird ausdrücklich umgerechnet; nach dem Speichern ist keine externe Datei nötig.",
"render.library_light_concepts": "Beleuchtungskonzepte",
"render.library_maps_help": "Built-in maps can be saved with pinned sources and local occurrence mapping. Temporary optical overrides remain a separate mode.",
"render.library_material_concepts": "Materialkonzepte",
"render.library_next": "Nächste Seite",
"render.library_physical_profiles": "Physical reference profiles",
"render.library_previous": "Vorherige Seite",
"render.library_quality_concepts": "Qualitätsspezifikationen",
"render.library_replace_environment": "Vorhandene Umgebung ausdrücklich ersetzen",
"render.library_replace_studio": "Replace saved lighting, environment and stage",
"render.library_results": "Passende Ressourcen",
"render.library_rig_help": "Fit once at an explicit center and physical extent. Complete rigs include passive cards; calibration blockers still refuse. Conservative emitter/card clearance is checked before saving.",
"render.library_rigs": "Erstellte Lichtaufbauten",
"render.library_save_maps": "Save maps to this setup",
"render.library_scene_bounds": "Erfasste Szenengrenzen verwenden",
"render.library_scenes": "Szenenspezifikationen",
"render.library_search": "Bibliothek durchsuchen",
"render.library_selene": "SELENE: opake Oberflächen",
"render.library_specification_only": "Nur Spezifikation: keine vollständig ausführbare Voreinstellung. Erforderliche Effekte, Ressourcen und Objektrollen müssen noch implementiert und gebunden werden.",
"render.library_surfaces": "Erstellte PBR-Oberflächen",
"render.library_target": "Zielkörper",
"render.library_title": "Render-Ressourcenbibliothek",
"render.library_uniform_ack": "Einheitliche Näherung ohne räumliche oder erweiterte Effekte verwenden",
"render.material_color_replace": "Basismap ersetzt Körperfarbe",
"render.material_limits": "Höchstens 128 Körperüberschreibungen, 32 getrennte Glaskörper, 64 Beschichtungen und 128 Texturbindungen. Identische Maps teilen Speicher; Geräte- und Gesamtspeichergrenzen bleiben aktiv.",
"render.material_metallic_map": "Metallanteil-Map (linear)",
"render.material_roughness_map": "Rauheitsmap (linear)",
"render.material_v_up": "Textur-V zeigt im Bild nach oben",
"render.navigation_help": "Die Kamera bewegt sich wie im Konstruieren (Einstellungen › Navigation). Setup speichern übernimmt die aktuelle Kamera.",
"render.opaque_image_summary": "Vorschau-PNG · opakes PBR · Transparenz durch Blending",
"render.opaque_preview_help": "Opakes PBR und einfache Farben; Transparenz durch Blending. Brechung, HDRI und finales Rendering sind noch nicht verfügbar.",
"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.passive_stage": "Render-only stage",
"render.passive_stage_help": "Finite passive panels belong only to rendered images. They do not add CAD bodies or mass. Placement is fixed after applying; intersecting product bounds refuse. CPU does not provide diffuse bounce lighting.",
"render.perspective": "Perspektivische Kamera",
"render.physical_acknowledge": "I understand this partial reference needs grade, condition and source review",
"render.physical_associations": "Physical candidates and roles",
"render.physical_copy_plan": "Copy plan with sources and omissions",
"render.physical_material_name": "Proposed reference material name",
"render.physical_omissions": "Properties not representable in the native card",
"render.physical_plan_only": "Read-only plan prepared. No material was created or assigned. Keep its extended profile and omissions when reviewing the native card.",
"render.physical_prepare_plan": "Prepare creation plan (does not apply)",
"render.physical_separate": "Appearance is not material identification. These are sourced references, not certified effective properties. Existing mass, engineering materials and optics are unchanged.",
"render.physical_unknown": "No numerical candidate is established. Grade, formulation or construction must be specified; unknown is not zero.",
"render.preview": "Integrierte Vorschau",
"render.preview_help": "Rasterdarstellung mit gemischter Transparenz. Brechung, HDRI und finales Rendering sind noch nicht verfügbar.",
"render.reload_settings": "Gespeicherte Einstellungen laden",
"render.replace_saved_surface": "Replace an existing saved binding for this target",
"render.restore_camera": "Gespeicherte Kamera laden",
"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.return_design": "Zurück zu Design",
"render.ribbon_backgrounds": "Hintergründe",
"render.ribbon_check": "Prüfen",
"render.ribbon_check_help": "Genau den aktuellen Renderentwurf prüfen. Kein stiller Rendererwechsel, keine Verkleinerung oder Ersetzung.",
"render.ribbon_edit_lights": "Licht ändern",
"render.ribbon_environment_help": "HDR-Beleuchtung, Kamerahintergründe und physische Bühnen sind getrennte Einstellungen mit ausdrücklicher Ersetzungsbestätigung.",
"render.ribbon_finish_dialog": "Zuerst den Datei- oder Bestätigungsdialog abschließen.",
"render.ribbon_frame": "Einpassen",
"render.ribbon_hdr_library": "HDR-Bibliothek",
"render.ribbon_job_active": "Ein Render läuft bereits; vor einem neuen Auftrag ausdrücklich abbrechen.",
"render.ribbon_light_rigs": "Lichtaufbauten",
"render.ribbon_lights_help": "Lichtaufbauten prüfen oder unterstützte Lichter bearbeiten; erst ausdrücklich Anwenden ersetzt die Einrichtung.",
"render.ribbon_materials": "Materialien",
"render.ribbon_materials_help": "Gemeinsamen Erscheinungsinspektor und Originaloberflächen öffnen. Optik ändert keine technischen Eigenschaften stillschweigend.",
"render.ribbon_no_active_job": "Kein aktiver Render zum Abbrechen.",
"render.ribbon_no_result": "Vor dem Export muss ein Render abgeschlossen sein.",
"render.ribbon_no_saved_camera": "Vor dem Wiederherstellen eine Aufnahme speichern.",
"render.ribbon_saved_camera": "Gespeicherte Kamera",
"render.ribbon_shots": "Aufnahmen",
"render.ribbon_studios": "Studios",
"render.ribbon_studios_help": "Implementierte Studios durchsuchen und Ersetzungen vor dem Anwenden prüfen. Konzepte bleiben gekennzeichnet.",
"render.ribbon_surface_maps": "Oberflächen",
"render.save_setup": "Setup speichern",
"render.saved_maps_runtime_conflict": "Saved surface bindings cannot be mixed with temporary optical overrides or an unsaved HDR. Save the HDR or remove the temporary inputs explicitly.",
"render.saved_surface_remove": "Remove saved binding",
"render.saved_surfaces": "Saved surface bindings",
"render.saved_surfaces_help": "Pinned built-in maps follow the selected occurrence in local millimeters. A replaced or missing target is an error, never an automatic reassignment.",
"render.setup_name": "Name des Setups",
"render.stage_panel_count": "Saved panels",
"render.unavailable_preview": "Dieses Setup verlangt ein nicht verfügbares Vorschauprofil.",
"render.unsaved": "Ungespeicherte Vorschauänderungen",
"render.use_preview": "Integrierte Vorschau verwenden",
@@ -1187,6 +1321,8 @@
"settings.msaa_applies_on_restart": "Die Kantenglättung wird beim nächsten Start von SolidCraft übernommen",
"settings.msaa_reset": "{setting}: {requested}× wird von diesem Grafikgerät nicht unterstützt; jetzt {reset}× (unterstützt: {supported}).",
"settings.msaa_unsupported": "Von diesem Grafikgerät nicht unterstützt",
"settings.reference_geometry_through_bodies": "Referenzgeometrie durch Körper hindurch anzeigen",
"settings.reference_geometry_through_bodies_help": "Konturen inaktiver Skizzen und Bezugsebenen über schattierten Körpern zeichnen",
"settings.reset_intro": "Diese Einstellungen funktionieren auf diesem Computer nicht und würden den Start von SolidCraft verhindern. Sie wurden daher geändert:",
"settings.reset_title": "Einstellungen wurden geändert",
"settings.supersampling_note": "Zeichnet die Ansicht in höherer Auflösung und rechnet sie herunter: glattere Kanten, Linien und Raster auf Bildschirmen unter 4K. Kostet GPU-Zeit: etwa 2,25-mal so viele Pixel bei 1,5× und 4-mal bei 2×.",
@@ -1237,12 +1373,115 @@
"toolbar.customize_note": "Registerkarte {tab}. Markieren Sie einen Befehl, um ihm eine Schaltfläche in der Werkzeugleiste zu geben. Sie können auch eine Zeile aus dem Aufklappmenü eines Panels auf die Werkzeugleiste ziehen, eine Schaltfläche zum Entfernen aus der Werkzeugleiste ziehen oder die Stecknadel einer Menüzeile verwenden.",
"toolbar.image": "BILD",
"toolbar.render": "RENDERN",
"toolbar.render_appearance": "ERSCHEINUNG",
"toolbar.render_camera": "KAMERA",
"toolbar.render_environment": "UMGEBUNG",
"toolbar.render_lighting": "BELEUCHTUNG",
"toolbar.render_scene": "SZENE",
"trace.points": "{count} Punkte entlang der Bahn",
"trace.stopped_by_contact": "Endet bei Probe {sample}: {a} würde {b} durchdringen",
"trace.stopped_by_loop": "Endet bei Probe {sample}: die Schleife über {joint} lässt sich nicht weiter schließen",
"ui.1_degree_of_freedom": "1 Freiheitsgrad",
"ui.3d_mouse_axes_reassigned": "Die Achsen sind in den Einstellungen neu zugeordnet, und das gilt auch nach dieser Kalibrierung.",
"ui.3d_mouse_button": "Taste {number}",
"ui.3d_mouse_button_nothing": "Nichts",
"ui.3d_mouse_button_settings": "3D-Maus-Einstellungen",
"ui.3d_mouse_buttons": "Tasten",
"ui.3d_mouse_buttons_hint": "Drücken Sie eine Taste an Ihrer 3D-Maus, um sie hier zu finden, und wählen Sie dann, was sie tut.",
"ui.3d_mouse_buttons_no_device": "Schließen Sie eine 3D-Maus an, um ihre Tasten zu belegen.",
"ui.3d_mouse_buttons_reset": "Standardbelegung wiederherstellen",
"ui.3d_mouse_calibrate": "Kalibrieren…",
"ui.3d_mouse_calibrate_tip": "Bewegen Sie die Kappe so, wie das Fenster es verlangt. So lernt SolidCraft, in welche Richtung und wie weit jede Achse dieser 3D-Maus geht. Wird für diese 3D-Maus gespeichert.",
"ui.3d_mouse_calibrate_title": "3D-Maus kalibrieren",
"ui.3d_mouse_calibrate_wait": "Bewegen Sie die 3D-Maus einmal, damit SolidCraft sie erkennt.",
"ui.3d_mouse_calibrated": "Kalibriert",
"ui.3d_mouse_calibration": "3D-Maus-Kalibrierung",
"ui.3d_mouse_calibration_done": "Fertig. Speichern Sie die Kalibrierung, damit sie immer gilt, wenn diese 3D-Maus angeschlossen ist.",
"ui.3d_mouse_calibration_no_response": "Eine Achse hat sich kaum bewegt und lässt sich nicht kalibrieren. Beginnen Sie von vorn und bewegen Sie die Kappe jedes Mal weiter.",
"ui.3d_mouse_circles_help": "Die Kreise zeigen, was das Gerät nach der Kalibrierung sendet. Das Testmodell zeigt, was die Ansicht daraus macht, nach Umkehren, Geschwindigkeit und Achsenzuordnung.",
"ui.3d_mouse_dead_zone": "Totzone",
"ui.3d_mouse_dead_zone_tip": "Wie weit sich die Kappe bewegen muss, bevor sich die Ansicht bewegt, als Anteil ihres vollen Wegs. Erhöhen Sie sie, wenn die Ansicht wandert, obwohl die Kappe ruht.",
"ui.3d_mouse_denied": "SolidCraft sieht eine 3D-Maus, darf sie aber nicht lesen. Die udev-Regel steht in den Einrichtungshinweisen zur 3D-Maus.",
"ui.3d_mouse_denied_short": "Gefunden, aber nicht lesbar",
"ui.3d_mouse_driven_by": "Welche kalibrierte Bewegung des Geräts diese Achse steuert. Wählen Sie eine, die schon eine andere Achse nutzt, werden die beiden getauscht.",
"ui.3d_mouse_driven_by_label": "gesteuert von",
"ui.3d_mouse_driver": "3D-Maus-Treiber",
"ui.3d_mouse_driver_auto": "Automatisch",
"ui.3d_mouse_driver_direct": "Direkt (hidraw)",
"ui.3d_mouse_driver_fallback": "Wird vorerst über spacenavd gelesen: das Gerät selbst ließ sich nicht öffnen.",
"ui.3d_mouse_driver_only_direct": "Das Gerät wird direkt gelesen; spacenavd läuft nicht",
"ui.3d_mouse_driver_only_spacenavd": "Lesen über spacenavd, die einzige verfügbare Möglichkeit",
"ui.3d_mouse_forget": "Kalibrierung verwerfen",
"ui.3d_mouse_ghost_left_viewport": "Ansicht links",
"ui.3d_mouse_ghost_left_window": "Fenster links",
"ui.3d_mouse_ghost_position": "Position",
"ui.3d_mouse_ghost_right_viewport": "Ansicht rechts",
"ui.3d_mouse_hint": "Ansicht gleichzeitig verschieben und drehen; Skizzen verwenden nur Schwenken und Zoomen.",
"ui.3d_mouse_kind_cap": "Zurück zur Mitte",
"ui.3d_mouse_kind_question": "Was für eine 3D-Maus ist es?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "Der Wert bleibt stehen, solange Sie halten, wie bei einem Gerät vom Typ „Zurück zur Mitte“.",
"ui.3d_mouse_looks_like_trackball": "Der Wert fällt zurück, sobald Sie aufhören, wie bei einem Trackball.",
"ui.3d_mouse_motion_rotation_x": "Rotation X",
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
"ui.3d_mouse_motion_translation_x": "Translation X",
"ui.3d_mouse_motion_translation_y": "Translation Y",
"ui.3d_mouse_motion_translation_z": "Translation Z",
"ui.3d_mouse_navigate": "Mit der 3D-Maus navigieren",
"ui.3d_mouse_none": "Keine 3D-Maus gefunden",
"ui.3d_mouse_not_calibrated": "Nicht kalibriert",
"ui.3d_mouse_offer": "Die Kalibrierung Ihrer 3D-Maus behebt vertauschte oder umgekehrte Achsen und stellt Geschwindigkeit und Gefühl ein.",
"ui.3d_mouse_offer_never": "Nicht mehr anzeigen",
"ui.3d_mouse_offer_swap": "Zu Hidraw wechseln (direkter Treiber)",
"ui.3d_mouse_reading": "Werte",
"ui.3d_mouse_reset_axes": "Achsenzuordnung zurücksetzen",
"ui.3d_mouse_reset_view": "Ansicht zurücksetzen",
"ui.3d_mouse_restore": "3D-Maus-Standardwerte wiederherstellen",
"ui.3d_mouse_restore_ask": "3D-Maus-Einstellungen auf die Standardwerte zurücksetzen? Kalibrierung und Tastenbelegung bleiben.",
"ui.3d_mouse_section": "3D-Maus",
"ui.3d_mouse_show_in_view": "3D-Maus in der 3D-Ansicht anzeigen",
"ui.3d_mouse_show_in_view_tip": "Ein Bild Ihrer 3D-Maus unten links, das sich mit ihr bewegt.",
"ui.3d_mouse_skip": "Mein Gerät kann das nicht",
"ui.3d_mouse_skip_tip": "Überspringt diese Bewegung in beide Richtungen; die Kalibrierung lässt sie aus.",
"ui.3d_mouse_speed": "Geschwindigkeit der 3D-Maus",
"ui.3d_mouse_step_lift": "Kappe nach oben ziehen, halten, dann loslassen.",
"ui.3d_mouse_step_press": "Kappe nach unten drücken, halten, dann loslassen.",
"ui.3d_mouse_step_pull": "Kappe zu sich heranziehen, halten, dann loslassen.",
"ui.3d_mouse_step_push": "Kappe von sich wegschieben, halten, dann loslassen.",
"ui.3d_mouse_step_rest": "Nehmen Sie die Hand von der Kappe und lassen Sie sie ruhen.",
"ui.3d_mouse_step_rest_trackball": "Nehmen Sie die Hand von der Kugel und lassen Sie sie ruhen.",
"ui.3d_mouse_step_ring_clockwise": "Drehen Sie den Scrollring ein paar Rasten im Uhrzeigersinn.",
"ui.3d_mouse_step_ring_counterclockwise": "Drehen Sie den Scrollring ein paar Rasten gegen den Uhrzeigersinn.",
"ui.3d_mouse_step_roll_away": "Rollen Sie die Kugel ein paarmal von sich weg und lassen Sie sie dann auslaufen.",
"ui.3d_mouse_step_roll_left": "Rollen Sie die Kugel ein paarmal nach links und lassen Sie sie dann auslaufen.",
"ui.3d_mouse_step_roll_right": "Rollen Sie die Kugel ein paarmal nach rechts und lassen Sie sie dann auslaufen.",
"ui.3d_mouse_step_roll_toward": "Rollen Sie die Kugel ein paarmal zu sich hin und lassen Sie sie dann auslaufen.",
"ui.3d_mouse_step_slide_left": "Kappe nach links schieben, halten, dann loslassen.",
"ui.3d_mouse_step_slide_right": "Kappe nach rechts schieben, halten, dann loslassen.",
"ui.3d_mouse_step_spin_clockwise": "Drehen Sie die Kugel ein paarmal im Uhrzeigersinn und lassen Sie sie dann auslaufen.",
"ui.3d_mouse_step_spin_counterclockwise": "Drehen Sie die Kugel ein paarmal gegen den Uhrzeigersinn und lassen Sie sie dann auslaufen.",
"ui.3d_mouse_step_tilt_away": "Oberseite der Kappe von sich weg kippen (die Ihnen nächste Kante geht nach oben), halten, dann loslassen.",
"ui.3d_mouse_step_tilt_left": "Kappe nach links kippen (linke Kante runter), halten, dann loslassen.",
"ui.3d_mouse_step_tilt_right": "Kappe nach rechts kippen (rechte Kante runter), halten, dann loslassen.",
"ui.3d_mouse_step_tilt_toward": "Oberseite der Kappe zu sich kippen (die Ihnen nächste Kante geht nach unten), halten, dann loslassen.",
"ui.3d_mouse_step_touch": "Legen Sie die Hand auf die Kappe, ohne zu drücken.",
"ui.3d_mouse_step_twist_clockwise": "Kappe im Uhrzeigersinn drehen, halten, dann loslassen.",
"ui.3d_mouse_step_twist_counterclockwise": "Kappe gegen den Uhrzeigersinn drehen, halten, dann loslassen.",
"ui.3d_mouse_switch_cap": "Als Zurück zur Mitte kalibrieren",
"ui.3d_mouse_switch_trackball": "Als Trackball kalibrieren",
"ui.3d_mouse_view_axis": "Ansichtsachse",
"ui.3d_mouse_view_off": "Aus",
"ui.3d_mouse_view_off_tip": "Das Profil des 3D-Maus-Treibers schaltet diese Achse ab, daher bewegt ihre Bewegung die Ansicht nicht. Wählen Sie hier die Bewegung einer anderen Achse, werden die beiden getauscht und sie bewegt die Ansicht nicht mehr.",
"ui.3d_mouse_view_orbit_sideways": "Orbit links / rechts",
"ui.3d_mouse_view_orbit_up_down": "Orbit hoch / runter",
"ui.3d_mouse_view_pan_sideways": "Schwenken links / rechts",
"ui.3d_mouse_view_pan_up_down": "Schwenken hoch / runter",
"ui.3d_mouse_view_roll": "Rollen",
"ui.3d_mouse_view_unmapped": "Achse {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "Warte auf Bewegung",
"ui.3d_mouse_wiggle": "Falls eine angeschlossen ist, bewegen Sie sie kurz.",
"ui.3d_sketch": "3D-Skizze",
"ui.activate": "Aktivieren",
"ui.active_component": "Aktive Komponente",
@@ -1472,6 +1711,7 @@
"ui.blocked": "Verdeckt",
"ui.bodies": "Körper",
"ui.bodies_all_if_none": "Körper (alle, wenn keine)",
"ui.bottom": "Unten",
"ui.box": "Block",
"ui.break": "Unterbrechen",
"ui.browser_bodies_one": "{count} Körper",
@@ -1627,6 +1867,7 @@
"ui.grid_follows_view_hint": "Seitenansichten zeigen das Raster auf der aufrechten Ebene, die ihnen zugewandt ist, statt auf dem Boden",
"ui.ground_shadow": "Bodenschatten",
"ui.groupSelected": "Auswahl gruppieren",
"ui.hatch": "Schraffur",
"ui.heading_add_ins": "ADD-INS",
"ui.heading_assemble": "ZUSAMMENBAU",
"ui.heading_box": "BLOCK",
@@ -1893,6 +2134,12 @@
"ui.sent_to_slicer": "An {slicer} gesendet",
"ui.sheet_gap": "Spalt",
"ui.shell": "Hülle",
"ui.shortcut_capture_add_key": "Eine normale Taste hinzufügen. Escape bricht ab.",
"ui.shortcut_capture_hint": "Eine Belegung anklicken, dann Modifikatortasten und eine Taste drücken. Weitere Modifikatoren innerhalb einer Sekunde hinzufügen; zum Bestätigen alle Tasten loslassen. Escape bricht ab.",
"ui.shortcut_capture_press": "Modifikatortasten und eine Taste drücken. Escape bricht ab.",
"ui.shortcut_capture_release": "Zum Bestätigen alle Tasten loslassen. Escape bricht ab.",
"ui.shortcut_capture_settling": "Tastenkürzel wird bestätigt… Escape bricht ab.",
"ui.shortcut_multiple_keys": "Modifikatortasten und nur eine weitere Taste verwenden. Zum Wiederholen die Belegung anklicken.",
"ui.show": "Einblenden",
"ui.showAll": "Alle einblenden",
"ui.showAllBodies": "Alle Volumenkörper einblenden",
@@ -1922,59 +2169,6 @@
"ui.smooth_wheel_zoom": "Sanftes Zoomen mit dem Rad",
"ui.smooth_wheel_zoom_tip": "Ausschalten, um jedes Radereignis sofort anzuwenden, ohne Nachlauf",
"ui.snap": "Einrasten",
"ui.space_mouse_axes_reassigned": "Die Achsen sind in den Einstellungen neu zugeordnet, und das gilt auch nach dieser Kalibrierung.",
"ui.space_mouse_calibrate": "Kalibrieren…",
"ui.space_mouse_calibrate_tip": "Bewegen Sie die Kappe so, wie das Fenster es verlangt. So lernt SolidCraft, in welche Richtung und wie weit jede Achse dieser 3D-Maus geht. Wird für diese 3D-Maus gespeichert.",
"ui.space_mouse_calibrate_title": "3D-Maus kalibrieren",
"ui.space_mouse_calibrated": "Kalibriert",
"ui.space_mouse_calibration": "3D-Maus-Kalibrierung",
"ui.space_mouse_calibration_done": "Fertig. Speichern Sie die Kalibrierung, damit sie immer gilt, wenn diese 3D-Maus angeschlossen ist.",
"ui.space_mouse_calibration_no_response": "Eine Achse hat sich kaum bewegt und lässt sich nicht kalibrieren. Beginnen Sie von vorn und bewegen Sie die Kappe jedes Mal weiter.",
"ui.space_mouse_dead_zone": "Totzone",
"ui.space_mouse_dead_zone_tip": "Wie weit sich die Kappe bewegen muss, bevor sich die Ansicht bewegt, als Anteil ihres vollen Wegs. Erhöhen Sie sie, wenn die Ansicht wandert, obwohl die Kappe ruht.",
"ui.space_mouse_driven_by": "Welche kalibrierte Bewegung des Geräts diese Achse steuert. Wählen Sie eine, die schon eine andere Achse nutzt, werden die beiden getauscht.",
"ui.space_mouse_driven_by_label": "gesteuert von",
"ui.space_mouse_driver": "3D-Maus-Treiber",
"ui.space_mouse_driver_auto": "Automatisch",
"ui.space_mouse_driver_direct": "Direkt (hidraw)",
"ui.space_mouse_driver_only_direct": "Das Gerät wird direkt gelesen; spacenavd läuft nicht",
"ui.space_mouse_driver_only_spacenavd": "Lesen über spacenavd, die einzige verfügbare Möglichkeit",
"ui.space_mouse_forget": "Kalibrierung vergessen",
"ui.space_mouse_hint": "Ansicht gleichzeitig verschieben und drehen; Skizzen verwenden nur Schwenken und Zoomen.",
"ui.space_mouse_motion_rotation_x": "Rotation X",
"ui.space_mouse_motion_rotation_y": "Rotation Y",
"ui.space_mouse_motion_rotation_z": "Rotation Z",
"ui.space_mouse_motion_translation_x": "Translation X",
"ui.space_mouse_motion_translation_y": "Translation Y",
"ui.space_mouse_motion_translation_z": "Translation Z",
"ui.space_mouse_navigate": "Mit SpaceNavigator / SpaceMouse navigieren",
"ui.space_mouse_none": "Keine 3D-Maus gefunden",
"ui.space_mouse_not_calibrated": "Nicht kalibriert",
"ui.space_mouse_reading": "Werte",
"ui.space_mouse_reset_axes": "Achsenzuordnung zurücksetzen",
"ui.space_mouse_step_lift": "Kappe nach oben ziehen, halten, dann loslassen.",
"ui.space_mouse_step_press": "Kappe nach unten drücken, halten, dann loslassen.",
"ui.space_mouse_step_pull": "Kappe zu sich heranziehen, halten, dann loslassen.",
"ui.space_mouse_step_push": "Kappe von sich wegschieben, halten, dann loslassen.",
"ui.space_mouse_step_rest": "Nehmen Sie die Hand von der Kappe und lassen Sie sie ruhen.",
"ui.space_mouse_step_slide_left": "Kappe nach links schieben, halten, dann loslassen.",
"ui.space_mouse_step_slide_right": "Kappe nach rechts schieben, halten, dann loslassen.",
"ui.space_mouse_step_tilt_back": "Kappe nach hinten kippen (Vorderkante hoch), halten, dann loslassen.",
"ui.space_mouse_step_tilt_forward": "Kappe nach vorn kippen (Vorderkante runter), halten, dann loslassen.",
"ui.space_mouse_step_tilt_left": "Kappe nach links kippen (linke Kante runter), halten, dann loslassen.",
"ui.space_mouse_step_tilt_right": "Kappe nach rechts kippen (rechte Kante runter), halten, dann loslassen.",
"ui.space_mouse_step_twist_clockwise": "Kappe im Uhrzeigersinn drehen, halten, dann loslassen.",
"ui.space_mouse_step_twist_counterclockwise": "Kappe gegen den Uhrzeigersinn drehen, halten, dann loslassen.",
"ui.space_mouse_view_axis": "Ansichtsachse",
"ui.space_mouse_view_off": "Aus",
"ui.space_mouse_view_off_tip": "Das Profil des 3D-Maus-Treibers schaltet diese Achse ab, daher bewegt ihre Bewegung die Ansicht nicht. Wählen Sie hier die Bewegung einer anderen Achse, werden die beiden getauscht und sie bewegt die Ansicht nicht mehr.",
"ui.space_mouse_view_orbit_sideways": "Orbit links / rechts",
"ui.space_mouse_view_orbit_up_down": "Orbit hoch / runter",
"ui.space_mouse_view_pan_sideways": "Schwenken links / rechts",
"ui.space_mouse_view_pan_up_down": "Schwenken hoch / runter",
"ui.space_mouse_view_roll": "Rollen",
"ui.space_mouse_view_unmapped": "Achse {n}",
"ui.space_mouse_view_zoom": "Zoom",
"ui.spacing": "Abstand",
"ui.spacing_2": "Abstand 2",
"ui.sphere": "Sphäre",
@@ -2016,7 +2210,7 @@
"ui.top": "Oben",
"ui.torus": "Torus",
"ui.total_angle": "Gesamtwinkel",
"ui.trackpad_pan_tip": "Trackpad-Gesten schwenken (aus: sie zoomen wie das Rad); Mausrad zoomt, Daumenrad schwenkt seitlich, Pinch zoomt immer",
"ui.trackpad_pan_tip": "Scrollen mit zwei Fingern verschiebt die Ansicht; bei Fusion dreht Umschalt + zwei Finger die Ansicht. Aus: Scrollen zoomt. Mausrad und Pinch-Geste zoomen; das Daumenrad verschiebt seitlich.",
"ui.trim": "Kürzen",
"ui.twist": "Verdrehung",
"ui.two_finger_scroll_pans": "Zwei-Finger-Scrollen schwenkt",
+77 -1
View File
@@ -7,6 +7,8 @@
"Modeled": "Modelled",
"cmd.construct_point_center": "Point At Centre Of Circle/Sphere/Torus",
"cmd.inspect_center_of_mass": "Centre of Mass",
"cmd.render_aging_describe": "Layered Surface Aging",
"cmd.render_effects_describe": "Lens and Creative Effects",
"cmd.sketch_arc_center_point": "Centre Point Arc",
"cmd.sketch_circle_center": "Centre Diameter Circle",
"cmd.sketch_rectangle_center": "Centre Rectangle",
@@ -14,8 +16,83 @@
"cmd.sketch_slot_center_point": "Centre Point Slot",
"cmd.sketch_slot_center_to_center": "Centre to Centre Slot",
"pattern.alignment_hint": "Counts include the original. Centre balances copies; Flip chooses the extra side for even totals.",
"render.aging_add": "Add aging layer",
"render.aging_airborne": "Airborne dust / participating medium",
"render.aging_airborne_help": "Airborne dust is a separate finite homogeneous scattering volume. It changes transport and raw EXR, not the surface-aging stack. Existing volume/glass/denoising restrictions remain explicit; no atmosphere is added automatically.",
"render.aging_all_bodies": "All eligible opaque bodies",
"render.aging_chips": "Chipped paint",
"render.aging_class_any": "All opaque material classes",
"render.aging_class_dielectric": "Nonmetallic surfaces only",
"render.aging_class_metal": "Metallic surfaces only",
"render.aging_color": "Layer colour · sRGB",
"render.aging_coverage": "Coverage",
"render.aging_dust": "Deposited dust",
"render.aging_empty": "No aging layers: the original surfaces remain unchanged.",
"render.aging_fading": "Faded coating",
"render.aging_grime": "Grime & staining",
"render.aging_help": "Combine up to eight optical layers, in order. Rust and patina are authored appearances, not age/corrosion simulations. Scale is in world millimetres. No geometry, mass or engineering property is changed; glass and passive stages are excluded.",
"render.aging_metallic": "Layer metallic fraction",
"render.aging_origin": "Field origin · world mm",
"render.aging_patina": "Copper-green patina",
"render.aging_refresh_targets": "Inspect current bodies",
"render.aging_roughness": "Layer roughness",
"render.aging_rust": "Oxidized iron / rust",
"render.aging_scale": "Pattern scale",
"render.aging_scratches": "Fine scratches",
"render.aging_target": "Target & coordinates",
"render.aging_target_help": "A named target must still match its captured index and name. Global masks are world-anchored; they do not follow moving parts. This is neither cavity detection nor true edge wear. Filter the list to reach additional bodies.",
"render.aging_target_search": "Filter body names",
"render.aging_upward": "Upward-facing deposition",
"render.backdrop_x": "Centre X",
"render.backdrop_y": "Centre Y",
"render.background_camera_section": "Camera background · PNG presentation",
"render.background_completed_frozen": "The displayed result is a frozen completed image. Editing a background does not repaint that result automatically. Use Regrade for camera-only changes, or Render for a new physical stage.",
"render.background_hdr_separate": "The current HDR resources illuminate and reflect on surfaces; they are not automatically displayed as a panorama behind the subject. Changing a gradient does not rotate or recolour the lighting.",
"render.background_lighting_section": "HDR illumination · separate from camera background",
"render.background_no_alpha_override": "A camera background is not added to transparent output silently. Raw-master regrading uses captured coverage and does not reapply denoising; the source EXR remains unchanged.",
"render.background_regrade": "Apply to retained raw master",
"render.background_stage_occlusion": "This shot includes physical stage geometry. A large floor or wall can fill the frame and hide a camera-only background. Clear or resize the stage deliberately, or change its physical reflectance. Regrading cannot remove captured geometry.",
"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.chromatic_offset": "Chromatic edge offset",
"render.effects_gpu_only": "These effects require GPU path tracing. Changing the page never switches the renderer automatically.",
"render.effects_help": "Combine ordered colour looks and image-space lens effects. Rendering and raw-master regrading use the same saved settings. Raw EXR stays unchanged. These are original artistic controls, not calibrated lens or branded-film emulations.",
"render.effects_reset_lens": "Reset lens effects",
"render.effects_seed": "Pattern seed",
"render.film_grain": "Photographic grain",
"render.flare_ghosts": "Ghost rings per source",
"render.flare_halo": "Flare halo",
"render.flare_streak": "Anamorphic-style streak",
"render.flare_strength": "Highlight flare strength",
"render.flare_threshold": "Scene-linear flare threshold",
"render.layer_down": "Move down",
"render.layer_enabled": "Layer enabled",
"render.layer_remove": "Remove layer",
"render.layer_strength": "Layer strength",
"render.layer_up": "Move up",
"render.lens_effects_help": "Flare uses up to four separated bright sources in the actual linear image. Ghosts, halo, streaks, chromatic offsets and radial distortion are artistic image-space approximations. Spatial effects need opaque output; outside samples extend the image edge. Grain is deterministic. No physics, EXR or geometry is altered.",
"render.lens_effects_title": "Lens-inspired & photographic",
"render.look_add": "Add colour look",
"render.look_bleach": "Bleach-bypass-inspired",
"render.look_cool": "Cool",
"render.look_cyanotype": "Cyanotype-inspired",
"render.look_faded": "Faded print",
"render.look_mono": "Monochrome",
"render.look_sepia": "Sepia",
"render.look_strength": "Look strength",
"render.look_teal_orange": "Teal / orange",
"render.look_title": "Creative look stack",
"render.look_warm": "Warm",
"render.opaque_preview_help": "Opaque PBR and simple colours; blended transparency. Refraction, HDRI and final rendering are not available yet.",
"render.page_aging": "Aging & contamination",
"render.page_background": "Backgrounds",
"render.page_effects": "Lens & finishing effects",
"render.radial_distortion": "Radial distortion",
"render.ribbon_aging": "Aging",
"render.ribbon_effects": "Effects",
"render.sharpening": "Detail sharpening",
"render.soft_focus": "Soft focus",
"sketch_text.align_center": "Centre",
"ui.3d_mouse_step_pull": "Pull the cap towards you, hold it, then let go.",
"ui.center": "Centre",
"ui.centerline": "Centreline",
"ui.customizeToolbar": "Customise Toolbar…",
@@ -24,7 +101,6 @@
"ui.layout_compact": "Compact",
"ui.layout_floating": "Floating",
"ui.ribbon_labels": "Show text under ribbon icons",
"ui.space_mouse_step_pull": "Pull the cap towards you, hold it, then let go.",
"ui.workspace_layout": "Workspace layout",
"ui.zoom_to_cursor_tip": "The wheel zooms towards the point under the cursor (off: towards the view's centre)",
"update.prefs_flatpak": "Flatpak updates this copy ({version}): run {command} or use your software centre. SolidCraft doesn't check."
+841 -56
View File
File diff suppressed because it is too large Load Diff
+768 -62
View File
File diff suppressed because it is too large Load Diff
+122 -62
View File
@@ -72,6 +72,7 @@
"Choose…": "Choisir…",
"Circular": "Circulaire",
"Circumference": "Circonférence",
"Clockwise": "Horaire",
"Close draft": "Fermer le brouillon",
"Collisions": "Collisions",
"Colour": "Couleur",
@@ -322,6 +323,7 @@
"Search commands": "Rechercher des commandes",
"Search this library": "Rechercher dans cette bibliothèque",
"Section": "Coupe",
"Section position": "Position de la section",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Sélectionner un autre élément : maintenir le bouton gauche immobile sur le modèle (ou clic droit › Sélectionner un autre…) pour lister tout ce qui se trouve sous le curseur ; cliquer de nouveau au même endroit pour sélectionner l'élément suivant. Double-cliquer avec le bouton du milieu pour revenir à l'accueil (Préférences › Navigation).",
"Select bodies (a face picks its body)": "Sélectionner des corps (une face sélectionne son corps)",
"Select bodies or faces instead": "Sélectionner plutôt des corps ou des faces",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft s'est fermé avant l'enregistrement de ces conceptions. Leur dernier enregistrement automatique peut être ouvert.",
"Specialise": "Spécialiser",
"Speed": "Vitesse",
"Square": "Carré",
"Stand it up": "Redresser",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Redresser : un quart de tour autour de X, sous forme d'une fonction Déplacer que vous pouvez annuler.",
"Stand this part up?": "Redresser cette pièce ?",
"Start angle": "Angle de départ",
"Start bulge": "Renflement de début",
"Start continuity": "Continuité de début",
"Step": "Étape",
@@ -412,6 +416,7 @@
"angle, height": "angle, hauteur",
"appearance.dielectric_ior": "Indice de réfraction (diélectrique)",
"appearance.metallic": "Métallique",
"appearance.only_available": "Apparences disponibles uniquement",
"appearance.pbr_help": "Voir cette surface dans l'espace Rendu ou dans un PNG du produit. Sans réfraction ni transmission.",
"appearance.pbr_opaque": "PBR opaque",
"appearance.roughness": "Rugosité",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Insérer un STEP",
"cmd.file_save_mesh": "Enregistrer comme maillage",
"cmd.file_slicers": "Slicers installés",
"cmd.frame_member": "Élément de structure",
"cmd.frame_profile_insert": "Insérer un profilé",
"cmd.frame_profile_list": "Bibliothèque de profilés",
"cmd.frame_profile_properties": "Propriétés du profilé",
"cmd.inspect_accessibility": "Analyse d'accessibilité",
"cmd.inspect_center_of_mass": "Centre de gravité",
"cmd.inspect_clearance": "Jeu",
@@ -569,6 +578,7 @@
"cmd.material_create": "Nouveau matériau physique",
"cmd.material_delete": "Supprimer le matériau physique",
"cmd.material_edit": "Modifier le matériau physique",
"cmd.material_inspect": "Inspecter le matériau physique",
"cmd.material_list": "Lister les matériaux",
"cmd.material_properties_directional": "Module d'élasticité directionnel",
"cmd.material_properties_validate": "Valider les données du matériau physique",
@@ -1044,13 +1054,13 @@
"sample.bracket.name": "Équerre en tôle",
"sample.enclosure.about": "Boîte en coque avec lèvre, bossages de vis, appui et clip",
"sample.enclosure.name": "Boîtier plastique",
"sample.flange.about": "Moyeu et bride avec un réseau circulaire piloté par un paramètre",
"sample.flange.about": "Moyeu et bride avec une répétition circulaire pilotée par un paramètre",
"sample.flange.name": "Bride boulonnée",
"sample.hinge.about": "Deux composants et une liaison pivot, entraînée à 60°",
"sample.hinge.name": "Assemblage de charnière",
"sample.plate.about": "Plaque paramétrique avec congés, perçages et un bossage alésé",
"sample.plate.name": "Plaque d'exemple",
"sample.vents.about": "Rainures en réseau rectangulaire : lignes, colonnes et pas sont des paramètres",
"sample.vents.about": "Rainures en répétition rectangulaire : lignes, colonnes et pas sont des paramètres",
"sample.vents.name": "Plaque d'aération",
"search.command_keys": "Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
"search.commands": "Commandes",
@@ -1072,6 +1082,8 @@
"settings.msaa_applies_on_restart": "L'anticrénelage s'appliquera au prochain démarrage de SolidCraft",
"settings.msaa_reset": "{setting} : {requested}× n'est pas pris en charge par ce périphérique graphique ; la valeur est désormais {reset}× (pris en charge : {supported}).",
"settings.msaa_unsupported": "Non pris en charge par ce périphérique graphique",
"settings.reference_geometry_through_bodies": "Afficher la géométrie de référence à travers les corps",
"settings.reference_geometry_through_bodies_help": "Afficher les contours des esquisses inactives et les plans de référence par-dessus les corps ombrés",
"settings.reset_intro": "Ces réglages ne fonctionnent pas sur cet ordinateur et empêcheraient SolidCraft de démarrer ; ils ont donc été modifiés :",
"settings.reset_title": "Des réglages ont été modifiés",
"settings.supersampling_note": "Dessine la vue à une résolution plus élevée puis la réduit : arêtes, lignes et grille plus lisses sur les écrans de définition inférieure à la 4K. Cela coûte du temps GPU : environ 2,25× plus de pixels à 1,5× et 4× plus à 2×.",
@@ -1127,7 +1139,106 @@
"trace.stopped_by_contact": "Se termine à l'échantillon {sample} : {a} traverserait {b}",
"trace.stopped_by_loop": "Se termine à l'échantillon {sample} : la boucle passant par {joint} ne peut plus se fermer",
"ui.1_degree_of_freedom": "1 degré de liberté",
"ui.3d_mouse_axes_reassigned": "Les axes sont réattribués dans les Préférences, et cela s'applique toujours en plus de ce calibrage.",
"ui.3d_mouse_button": "Bouton {number}",
"ui.3d_mouse_button_nothing": "Rien",
"ui.3d_mouse_button_settings": "Réglages de la souris 3D",
"ui.3d_mouse_buttons": "Boutons",
"ui.3d_mouse_buttons_hint": "Appuyez sur un bouton de votre souris 3D pour le trouver ici, puis choisissez ce qu'il fait.",
"ui.3d_mouse_buttons_no_device": "Branchez une souris 3D pour choisir ce que font ses boutons.",
"ui.3d_mouse_buttons_reset": "Rétablir les boutons par défaut",
"ui.3d_mouse_calibrate": "Calibrer…",
"ui.3d_mouse_calibrate_tip": "Déplacez le capuchon comme la fenêtre le demande, pour que SolidCraft apprenne dans quel sens et jusqu'où va chaque axe de cette souris 3D. Enregistré pour cette souris 3D.",
"ui.3d_mouse_calibrate_title": "Calibrer la souris 3D",
"ui.3d_mouse_calibrate_wait": "Bougez la souris 3D une fois pour que SolidCraft la voie.",
"ui.3d_mouse_calibrated": "Calibrée",
"ui.3d_mouse_calibration": "Calibrage de la souris 3D",
"ui.3d_mouse_calibration_done": "Terminé. Enregistrez-le pour l'utiliser chaque fois que cette souris 3D est connectée.",
"ui.3d_mouse_calibration_no_response": "Un axe a à peine bougé, il ne peut donc pas être calibré. Recommencez en déplaçant le capuchon plus loin à chaque fois.",
"ui.3d_mouse_circles_help": "Les cercles montrent ce que l'appareil envoie après calibrage. Le modèle d'essai montre ce que la vue en fait, après inversion, vitesse et affectation des axes.",
"ui.3d_mouse_dead_zone": "Zone morte",
"ui.3d_mouse_dead_zone_tip": "De combien le capuchon doit bouger avant que la vue ne bouge, en part de sa course complète. Augmentez-la si la vue dérive alors que le capuchon est au repos.",
"ui.3d_mouse_denied": "SolidCraft voit une souris 3D mais n'a pas le droit de la lire. La règle udev est dans les notes d'installation de la souris 3D.",
"ui.3d_mouse_denied_short": "Trouvée, mais illisible",
"ui.3d_mouse_driven_by": "Quel mouvement calibré de l'appareil pilote cet axe. Choisir un mouvement déjà utilisé par un autre axe les échange.",
"ui.3d_mouse_driven_by_label": "piloté par",
"ui.3d_mouse_driver": "Pilote de la souris 3D",
"ui.3d_mouse_driver_auto": "Automatique",
"ui.3d_mouse_driver_direct": "Direct (hidraw)",
"ui.3d_mouse_driver_fallback": "Lecture via spacenavd pour l'instant : l'appareil lui-même n'a pas pu être ouvert.",
"ui.3d_mouse_driver_only_direct": "Lecture directe du périphérique ; spacenavd n'est pas lancé",
"ui.3d_mouse_driver_only_spacenavd": "Lecture via spacenavd, seule possibilité disponible",
"ui.3d_mouse_forget": "Oublier le calibrage",
"ui.3d_mouse_ghost_left_viewport": "Vue à gauche",
"ui.3d_mouse_ghost_left_window": "Fenêtre à gauche",
"ui.3d_mouse_ghost_position": "Position",
"ui.3d_mouse_ghost_right_viewport": "Vue à droite",
"ui.3d_mouse_hint": "Déplace et fait pivoter la vue en même temps ; dans les esquisses, seuls le panoramique et le zoom sont utilisés.",
"ui.3d_mouse_kind_cap": "Retour au centre",
"ui.3d_mouse_kind_question": "Quel type de souris 3D est-ce ?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "La valeur reste tant que vous maintenez, comme sur un appareil de type « Retour au centre ».",
"ui.3d_mouse_looks_like_trackball": "La valeur retombe dès que vous arrêtez de bouger, comme sur un trackball.",
"ui.3d_mouse_motion_rotation_x": "Rotation X",
"ui.3d_mouse_motion_rotation_y": "Rotation Y",
"ui.3d_mouse_motion_rotation_z": "Rotation Z",
"ui.3d_mouse_motion_translation_x": "Translation X",
"ui.3d_mouse_motion_translation_y": "Translation Y",
"ui.3d_mouse_motion_translation_z": "Translation Z",
"ui.3d_mouse_navigate": "Naviguer avec la souris 3D",
"ui.3d_mouse_none": "Aucune souris 3D trouvée",
"ui.3d_mouse_not_calibrated": "Non calibrée",
"ui.3d_mouse_offer": "Calibrer votre souris 3D corrige les axes échangés ou inversés et règle sa vitesse et sa sensation.",
"ui.3d_mouse_offer_never": "Ne plus afficher",
"ui.3d_mouse_offer_swap": "Passer à Hidraw (pilote direct)",
"ui.3d_mouse_reading": "Lecture",
"ui.3d_mouse_reset_axes": "Réinitialiser l'attribution des axes",
"ui.3d_mouse_reset_view": "Réinitialiser la vue",
"ui.3d_mouse_restore": "Rétablir les réglages de la souris 3D",
"ui.3d_mouse_restore_ask": "Remettre les réglages de la souris 3D par défaut ? Votre calibrage et l'assignation des boutons sont conservés.",
"ui.3d_mouse_section": "Souris 3D",
"ui.3d_mouse_show_in_view": "Afficher la souris 3D dans la vue 3D",
"ui.3d_mouse_show_in_view_tip": "Une image de votre souris 3D dans le coin inférieur gauche, qui bouge quand vous la bougez.",
"ui.3d_mouse_skip": "Mon appareil ne peut pas le faire",
"ui.3d_mouse_skip_tip": "Ignore ce mouvement dans les deux sens ; la calibration le laisse de côté.",
"ui.3d_mouse_speed": "Vitesse de la souris 3D",
"ui.3d_mouse_step_lift": "Soulevez le capuchon, maintenez, puis relâchez.",
"ui.3d_mouse_step_press": "Appuyez sur le capuchon, maintenez, puis relâchez.",
"ui.3d_mouse_step_pull": "Tirez le capuchon vers vous, maintenez, puis relâchez.",
"ui.3d_mouse_step_push": "Poussez le capuchon loin de vous, maintenez, puis relâchez.",
"ui.3d_mouse_step_rest": "Lâchez le capuchon et laissez-le immobile.",
"ui.3d_mouse_step_rest_trackball": "Lâchez la boule et laissez-la immobile.",
"ui.3d_mouse_step_ring_clockwise": "Tournez l'anneau de défilement de quelques crans dans le sens horaire.",
"ui.3d_mouse_step_ring_counterclockwise": "Tournez l'anneau de défilement de quelques crans dans le sens antihoraire.",
"ui.3d_mouse_step_roll_away": "Faites rouler la boule loin de vous plusieurs fois, puis laissez-la s'arrêter.",
"ui.3d_mouse_step_roll_left": "Faites rouler la boule vers la gauche plusieurs fois, puis laissez-la s'arrêter.",
"ui.3d_mouse_step_roll_right": "Faites rouler la boule vers la droite plusieurs fois, puis laissez-la s'arrêter.",
"ui.3d_mouse_step_roll_toward": "Faites rouler la boule vers vous plusieurs fois, puis laissez-la s'arrêter.",
"ui.3d_mouse_step_slide_left": "Glissez le capuchon vers la gauche, maintenez, puis relâchez.",
"ui.3d_mouse_step_slide_right": "Glissez le capuchon vers la droite, maintenez, puis relâchez.",
"ui.3d_mouse_step_spin_clockwise": "Faites tourner la boule dans le sens horaire plusieurs fois, puis laissez-la s'arrêter.",
"ui.3d_mouse_step_spin_counterclockwise": "Faites tourner la boule dans le sens antihoraire plusieurs fois, puis laissez-la s'arrêter.",
"ui.3d_mouse_step_tilt_away": "Inclinez le haut du capuchon loin de vous (le bord le plus proche monte), maintenez, puis relâchez.",
"ui.3d_mouse_step_tilt_left": "Inclinez le capuchon vers la gauche (bord gauche vers le bas), maintenez, puis relâchez.",
"ui.3d_mouse_step_tilt_right": "Inclinez le capuchon vers la droite (bord droit vers le bas), maintenez, puis relâchez.",
"ui.3d_mouse_step_tilt_toward": "Inclinez le haut du capuchon vers vous (le bord le plus proche descend), maintenez, puis relâchez.",
"ui.3d_mouse_step_touch": "Posez la main sur le capuchon sans le pousser.",
"ui.3d_mouse_step_twist_clockwise": "Tournez le capuchon dans le sens horaire, maintenez, puis relâchez.",
"ui.3d_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
"ui.3d_mouse_switch_cap": "Calibrer comme retour au centre",
"ui.3d_mouse_switch_trackball": "Calibrer comme un trackball",
"ui.3d_mouse_view_axis": "Axe de la vue",
"ui.3d_mouse_view_off": "Désactivé",
"ui.3d_mouse_view_off_tip": "Le profil du pilote de la souris 3D désactive cet axe : son mouvement ne déplace pas la vue. Choisir ici le mouvement d'un autre axe les échange, et ce mouvement cesse de déplacer la vue.",
"ui.3d_mouse_view_orbit_sideways": "Orbite gauche / droite",
"ui.3d_mouse_view_orbit_up_down": "Orbite haut / bas",
"ui.3d_mouse_view_pan_sideways": "Panoramique gauche / droite",
"ui.3d_mouse_view_pan_up_down": "Panoramique haut / bas",
"ui.3d_mouse_view_roll": "Roulis",
"ui.3d_mouse_view_unmapped": "Axe {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "En attente de mouvement",
"ui.3d_mouse_wiggle": "Si vous en avez une de branchée, remuez-la.",
"ui.3d_sketch": "Esquisse 3D",
"ui.activate": "Activer",
"ui.active_component": "Composant actif",
@@ -1357,6 +1468,7 @@
"ui.blocked": "Inaccessible",
"ui.bodies": "Corps",
"ui.bodies_all_if_none": "Corps (tous si aucun)",
"ui.bottom": "Dessous",
"ui.box": "Boîte",
"ui.break": "Fractionner",
"ui.browser_bodies_one": "{count} corps",
@@ -1374,7 +1486,7 @@
"ui.chamfer_type": "Type de chanfrein",
"ui.change_workspace": "Changer d'espace de travail",
"ui.circle": "Cercle",
"ui.circular_pattern": "Réseau circulaire",
"ui.circular_pattern": "Répétition circulaire",
"ui.clear": "Effacer",
"ui.clear_recent": "Effacer les récents",
"ui.clear_the_selection": "Effacer la sélection",
@@ -1509,11 +1621,12 @@
"ui.grid_follows_view_hint": "Dans les vues de côté, la grille s'affiche sur le plan vertical qui leur fait face plutôt que sur le sol",
"ui.ground_shadow": "Ombre au sol",
"ui.groupSelected": "Grouper la sélection",
"ui.hatch": "Hachures",
"ui.heading_add_ins": "COMPLÉMENTS",
"ui.heading_assemble": "ASSEMBLER",
"ui.heading_box": "BOÎTE",
"ui.heading_chamfer": "CHANFREIN",
"ui.heading_circular_pattern": "RÉSEAU CIRCULAIRE",
"ui.heading_circular_pattern": "RÉPÉTITION CIRCULAIRE",
"ui.heading_coil": "HÉLICE",
"ui.heading_combine": "COMBINER",
"ui.heading_configure": "CONFIGURER",
@@ -1546,14 +1659,14 @@
"ui.heading_offset_plane": "PLAN DÉCALÉ",
"ui.heading_ok": "OK",
"ui.heading_parameters": "PARAMÈTRES",
"ui.heading_pattern": "RÉSEAU",
"ui.heading_pattern_on_path": "RÉSEAU SUR TRAJECTOIRE",
"ui.heading_pattern": "RÉPÉTITION",
"ui.heading_pattern_on_path": "RÉPÉTITION SUR TRAJECTOIRE",
"ui.heading_pipe": "TUYAU",
"ui.heading_plane_at_angle": "PLAN INCLINÉ",
"ui.heading_plastic": "PLASTIQUE",
"ui.heading_position": "POSITION",
"ui.heading_prepare": "PRÉPARER",
"ui.heading_rectangular_pattern": "RÉSEAU RECTANGULAIRE",
"ui.heading_rectangular_pattern": "RÉPÉTITION RECTANGULAIRE",
"ui.heading_revolve": "RÉVOLUTION",
"ui.heading_right": "DROITE",
"ui.heading_save_mesh": "ENREGISTRER COMME MAILLAGE",
@@ -1683,7 +1796,7 @@
"ui.part_size_shared": "Reconstruit la pièce en remplaçant les entités ajoutées. Toutes les occurrences de ce composant sont mises à jour.",
"ui.paste": "Coller",
"ui.path": "Trajectoire",
"ui.pattern_on_path": "Réseau sur trajectoire",
"ui.pattern_on_path": "Répétition sur trajectoire",
"ui.perpendicular": "Perpendicularité",
"ui.perspective_view": "Vue en perspective",
"ui.pinToShortcuts": "Épingler aux raccourcis",
@@ -1720,7 +1833,7 @@
"ui.rail_fit": "Ajustement au rail",
"ui.reachable": "Accessible",
"ui.rectangle": "Rectangle",
"ui.rectangular_pattern": "Réseau rectangulaire",
"ui.rectangular_pattern": "Répétition rectangulaire",
"ui.redo": "Rétablir",
"ui.relink_design_changed": "L’assemblage a changé pendant le choix d’une source. Relancez Relier à nouveau la source.",
"ui.relink_done": "Source reliée à nouveau pour toutes les instances.",
@@ -1799,59 +1912,6 @@
"ui.smooth_wheel_zoom": "Zoom fluide à la molette",
"ui.smooth_wheel_zoom_tip": "Désactiver pour appliquer chaque cran de molette immédiatement, sans lissage",
"ui.snap": "Accrochage",
"ui.space_mouse_axes_reassigned": "Les axes sont réattribués dans les Préférences, et cela s'applique toujours en plus de ce calibrage.",
"ui.space_mouse_calibrate": "Calibrer…",
"ui.space_mouse_calibrate_tip": "Déplacez le capuchon comme la fenêtre le demande, pour que SolidCraft apprenne dans quel sens et jusqu'où va chaque axe de cette souris 3D. Enregistré pour cette souris 3D.",
"ui.space_mouse_calibrate_title": "Calibrer la souris 3D",
"ui.space_mouse_calibrated": "Calibrée",
"ui.space_mouse_calibration": "Calibrage de la souris 3D",
"ui.space_mouse_calibration_done": "Terminé. Enregistrez-le pour l'utiliser chaque fois que cette souris 3D est connectée.",
"ui.space_mouse_calibration_no_response": "Un axe a à peine bougé, il ne peut donc pas être calibré. Recommencez en déplaçant le capuchon plus loin à chaque fois.",
"ui.space_mouse_dead_zone": "Zone morte",
"ui.space_mouse_dead_zone_tip": "De combien le capuchon doit bouger avant que la vue ne bouge, en part de sa course complète. Augmentez-la si la vue dérive alors que le capuchon est au repos.",
"ui.space_mouse_driven_by": "Quel mouvement calibré de l'appareil pilote cet axe. Choisir un mouvement déjà utilisé par un autre axe les échange.",
"ui.space_mouse_driven_by_label": "piloté par",
"ui.space_mouse_driver": "Pilote de la souris 3D",
"ui.space_mouse_driver_auto": "Automatique",
"ui.space_mouse_driver_direct": "Direct (hidraw)",
"ui.space_mouse_driver_only_direct": "Lecture directe du périphérique ; spacenavd n'est pas lancé",
"ui.space_mouse_driver_only_spacenavd": "Lecture via spacenavd, seule possibilité disponible",
"ui.space_mouse_forget": "Oublier le calibrage",
"ui.space_mouse_hint": "Déplace et fait pivoter la vue en même temps ; dans les esquisses, seuls le panoramique et le zoom sont utilisés.",
"ui.space_mouse_motion_rotation_x": "Rotation X",
"ui.space_mouse_motion_rotation_y": "Rotation Y",
"ui.space_mouse_motion_rotation_z": "Rotation Z",
"ui.space_mouse_motion_translation_x": "Translation X",
"ui.space_mouse_motion_translation_y": "Translation Y",
"ui.space_mouse_motion_translation_z": "Translation Z",
"ui.space_mouse_navigate": "Naviguer avec SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Aucune souris 3D trouvée",
"ui.space_mouse_not_calibrated": "Non calibrée",
"ui.space_mouse_reading": "Lecture",
"ui.space_mouse_reset_axes": "Réinitialiser l'attribution des axes",
"ui.space_mouse_step_lift": "Soulevez le capuchon, maintenez, puis relâchez.",
"ui.space_mouse_step_press": "Appuyez sur le capuchon, maintenez, puis relâchez.",
"ui.space_mouse_step_pull": "Tirez le capuchon vers vous, maintenez, puis relâchez.",
"ui.space_mouse_step_push": "Poussez le capuchon loin de vous, maintenez, puis relâchez.",
"ui.space_mouse_step_rest": "Lâchez le capuchon et laissez-le immobile.",
"ui.space_mouse_step_slide_left": "Glissez le capuchon vers la gauche, maintenez, puis relâchez.",
"ui.space_mouse_step_slide_right": "Glissez le capuchon vers la droite, maintenez, puis relâchez.",
"ui.space_mouse_step_tilt_back": "Inclinez le capuchon vers l'arrière (bord avant vers le haut), maintenez, puis relâchez.",
"ui.space_mouse_step_tilt_forward": "Inclinez le capuchon vers l'avant (bord avant vers le bas), maintenez, puis relâchez.",
"ui.space_mouse_step_tilt_left": "Inclinez le capuchon vers la gauche (bord gauche vers le bas), maintenez, puis relâchez.",
"ui.space_mouse_step_tilt_right": "Inclinez le capuchon vers la droite (bord droit vers le bas), maintenez, puis relâchez.",
"ui.space_mouse_step_twist_clockwise": "Tournez le capuchon dans le sens horaire, maintenez, puis relâchez.",
"ui.space_mouse_step_twist_counterclockwise": "Tournez le capuchon dans le sens antihoraire, maintenez, puis relâchez.",
"ui.space_mouse_view_axis": "Axe de la vue",
"ui.space_mouse_view_off": "Désactivé",
"ui.space_mouse_view_off_tip": "Le profil du pilote de la souris 3D désactive cet axe : son mouvement ne déplace pas la vue. Choisir ici le mouvement d'un autre axe les échange, et ce mouvement cesse de déplacer la vue.",
"ui.space_mouse_view_orbit_sideways": "Orbite gauche / droite",
"ui.space_mouse_view_orbit_up_down": "Orbite haut / bas",
"ui.space_mouse_view_pan_sideways": "Panoramique gauche / droite",
"ui.space_mouse_view_pan_up_down": "Panoramique haut / bas",
"ui.space_mouse_view_roll": "Roulis",
"ui.space_mouse_view_unmapped": "Axe {n}",
"ui.space_mouse_view_zoom": "Zoom",
"ui.spacing": "Espacement",
"ui.spacing_2": "Espacement 2",
"ui.sphere": "Sphère",
+111 -53
View File
@@ -72,6 +72,7 @@
"Choose…": "Escoller…",
"Circular": "Circular",
"Circumference": "Circunferencia",
"Clockwise": "Horario",
"Close draft": "Pechar o borrador",
"Collisions": "Colisións",
"Colour": "Cor",
@@ -322,6 +323,7 @@
"Search commands": "Buscar ordes",
"Search this library": "Buscar nesta biblioteca",
"Section": "Sección",
"Section position": "Posición da sección",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleccionar outro: mantén o botón esquerdo quieto sobre o modelo (ou clic dereito › Seleccionar outro…) para listar todo o que hai baixo o cursor; fai clic de novo no mesmo punto para seleccionar o seguinte. Fai dobre clic co botón central para ir ao Inicio (Preferencias › Navegación).",
"Select bodies (a face picks its body)": "Selecciona corpos (unha cara escolle o seu corpo)",
"Select bodies or faces instead": "Seleccionar corpos ou caras no seu lugar",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft pechouse antes de gardar estes deseños. Pódese abrir a súa última gravación automática.",
"Specialise": "Especializar",
"Speed": "Velocidade",
"Square": "Cadrado",
"Stand it up": "Poñela de pé",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Poñela de pé: un cuarto de volta arredor de X, como unha operación Mover que podes desfacer.",
"Stand this part up?": "Poñer esta peza de pé?",
"Start angle": "Ángulo inicial",
"Start bulge": "Abaulamento inicial",
"Start continuity": "Continuidade inicial",
"Step": "Paso",
@@ -412,6 +416,7 @@
"angle, height": "ángulo, altura",
"appearance.dielectric_ior": "IOR dieléctrico",
"appearance.metallic": "Metálico",
"appearance.only_available": "Só aparencias dispoñibles",
"appearance.pbr_help": "Ve esta superficie en Render ou nun PNG de produto. Sen refracción nin transmisión.",
"appearance.pbr_opaque": "PBR opaco",
"appearance.roughness": "Rugosidade",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Inserir STEP",
"cmd.file_save_mesh": "Gardar como malla",
"cmd.file_slicers": "Laminadores instalados",
"cmd.frame_member": "Membro estrutural",
"cmd.frame_profile_insert": "Inserir perfil",
"cmd.frame_profile_list": "Biblioteca de perfís",
"cmd.frame_profile_properties": "Propiedades do perfil",
"cmd.inspect_accessibility": "Análise de accesibilidade",
"cmd.inspect_center_of_mass": "Centro de masas",
"cmd.inspect_clearance": "Folga",
@@ -569,6 +578,7 @@
"cmd.material_create": "Material físico novo",
"cmd.material_delete": "Eliminar o material físico",
"cmd.material_edit": "Editar o material físico",
"cmd.material_inspect": "Inspeccionar o material físico",
"cmd.material_list": "Listar os materiais",
"cmd.material_properties_directional": "Módulo elástico direccional",
"cmd.material_properties_validate": "Validar os datos do material físico",
@@ -1121,7 +1131,106 @@
"trace.stopped_by_contact": "Remata na mostra {sample}: {a} atravesaría {b}",
"trace.stopped_by_loop": "Remata na mostra {sample}: o bucle a través de {joint} non pode pecharse máis",
"ui.1_degree_of_freedom": "1 grao de liberdade",
"ui.3d_mouse_axes_reassigned": "Os eixes están reasignados en Preferencias, e iso segue aplicándose sobre esta calibración.",
"ui.3d_mouse_button": "Botón {number}",
"ui.3d_mouse_button_nothing": "Nada",
"ui.3d_mouse_button_settings": "Axustes do rato 3D",
"ui.3d_mouse_buttons": "Botóns",
"ui.3d_mouse_buttons_hint": "Preme un botón do teu rato 3D para atopalo aquí e escolle que fai.",
"ui.3d_mouse_buttons_no_device": "Conecta un rato 3D para escoller que fan os seus botóns.",
"ui.3d_mouse_buttons_reset": "Restaurar os botóns predeterminados",
"ui.3d_mouse_calibrate": "Calibrar…",
"ui.3d_mouse_calibrate_tip": "Move o capuchón como pide a xanela para que SolidCraft aprenda cara a onde e ata onde vai cada eixe deste rato 3D. Gárdase para este rato 3D.",
"ui.3d_mouse_calibrate_title": "Calibrar o rato 3D",
"ui.3d_mouse_calibrate_wait": "Move o rato 3D unha vez para que SolidCraft o vexa.",
"ui.3d_mouse_calibrated": "Calibrado",
"ui.3d_mouse_calibration": "Calibración do rato 3D",
"ui.3d_mouse_calibration_done": "Feito. Gárdaa para usala sempre que este rato 3D estea conectado.",
"ui.3d_mouse_calibration_no_response": "Un eixe case non se moveu, así que non se pode calibrar. Volve comezar e move o capuchón máis lonxe cada vez.",
"ui.3d_mouse_circles_help": "Os círculos mostran o que envía o dispositivo tras a calibración. O modelo de proba mostra o que fai a vista con iso, tras a inversión, a velocidade e a asignación de eixes.",
"ui.3d_mouse_dead_zone": "Zona morta",
"ui.3d_mouse_dead_zone_tip": "Canto ten que moverse o capuchón antes de que se mova a vista, como parte do seu percorrido completo. Auméntaa se a vista se despraza co capuchón en repouso.",
"ui.3d_mouse_denied": "SolidCraft ve un rato 3D, pero non ten permiso para lelo. A regra udev está nas notas de configuración do rato 3D.",
"ui.3d_mouse_denied_short": "Atopado, pero non se pode ler",
"ui.3d_mouse_driven_by": "Que movemento calibrado do dispositivo controla este eixe. Se escolles un que xa usa outro eixe, intercámbianse.",
"ui.3d_mouse_driven_by_label": "controlado por",
"ui.3d_mouse_driver": "Controlador do rato 3D",
"ui.3d_mouse_driver_auto": "Automático",
"ui.3d_mouse_driver_direct": "Directo (hidraw)",
"ui.3d_mouse_driver_fallback": "Lendo polo de agora a través de spacenavd: non se puido abrir o dispositivo.",
"ui.3d_mouse_driver_only_direct": "Lectura directa do dispositivo; spacenavd non está en execución",
"ui.3d_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, a única opción dispoñible",
"ui.3d_mouse_forget": "Esquecer a calibración",
"ui.3d_mouse_ghost_left_viewport": "Vista esquerda",
"ui.3d_mouse_ghost_left_window": "Xanela esquerda",
"ui.3d_mouse_ghost_position": "Posición",
"ui.3d_mouse_ghost_right_viewport": "Vista dereita",
"ui.3d_mouse_hint": "Move e rota a vista á vez; os bosquexos só usan desprazamento e zoom.",
"ui.3d_mouse_kind_cap": "Retorno ao centro",
"ui.3d_mouse_kind_question": "Que tipo de rato 3D é?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "A lectura mantense mentres a suxeitas, como nun dispositivo de tipo «Retorno ao centro».",
"ui.3d_mouse_looks_like_trackball": "A lectura volve atrás en canto deixas de moverte, como nun trackball.",
"ui.3d_mouse_motion_rotation_x": "Rotación X",
"ui.3d_mouse_motion_rotation_y": "Rotación Y",
"ui.3d_mouse_motion_rotation_z": "Rotación Z",
"ui.3d_mouse_motion_translation_x": "Translación X",
"ui.3d_mouse_motion_translation_y": "Translación Y",
"ui.3d_mouse_motion_translation_z": "Translación Z",
"ui.3d_mouse_navigate": "Navegar co rato 3D",
"ui.3d_mouse_none": "Non se atopou ningún rato 3D",
"ui.3d_mouse_not_calibrated": "Sen calibrar",
"ui.3d_mouse_offer": "Calibrar o teu rato 3D corrixe os eixes trocados ou invertidos e axusta a súa velocidade e sensación.",
"ui.3d_mouse_offer_never": "Non mostrar de novo",
"ui.3d_mouse_offer_swap": "Cambiar a Hidraw (controlador directo)",
"ui.3d_mouse_reading": "Lectura",
"ui.3d_mouse_reset_axes": "Restablecer asignación de eixes",
"ui.3d_mouse_reset_view": "Restablecer vista",
"ui.3d_mouse_restore": "Restaurar valores do rato 3D",
"ui.3d_mouse_restore_ask": "Volver aos valores predeterminados da configuración do rato 3D? A calibración e a asignación de botóns consérvanse.",
"ui.3d_mouse_section": "Rato 3D",
"ui.3d_mouse_show_in_view": "Mostrar o rato 3D na vista 3D",
"ui.3d_mouse_show_in_view_tip": "Unha imaxe do teu rato 3D na esquina inferior esquerda que se move cando o moves.",
"ui.3d_mouse_skip": "O meu dispositivo non fai isto",
"ui.3d_mouse_skip_tip": "Omite este movemento nos dous sentidos; a calibración déixao fóra.",
"ui.3d_mouse_speed": "Velocidade do rato 3D",
"ui.3d_mouse_step_lift": "Levanta o capuchón, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_press": "Preme o capuchón cara abaixo, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_pull": "Tira do capuchón cara a ti, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_push": "Empurra o capuchón lonxe de ti, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_rest": "Solta o capuchón e déixao quieto.",
"ui.3d_mouse_step_rest_trackball": "Solta a bóla e déixaa quieta.",
"ui.3d_mouse_step_ring_clockwise": "Xira o anel de desprazamento uns clics no sentido horario.",
"ui.3d_mouse_step_ring_counterclockwise": "Xira o anel de desprazamento uns clics no sentido antihorario.",
"ui.3d_mouse_step_roll_away": "Fai rodar a bóla lonxe de ti unhas cantas veces e déixaa parar.",
"ui.3d_mouse_step_roll_left": "Fai rodar a bóla á esquerda unhas cantas veces e déixaa parar.",
"ui.3d_mouse_step_roll_right": "Fai rodar a bóla á dereita unhas cantas veces e déixaa parar.",
"ui.3d_mouse_step_roll_toward": "Fai rodar a bóla cara a ti unhas cantas veces e déixaa parar.",
"ui.3d_mouse_step_slide_left": "Despraza o capuchón á esquerda, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_slide_right": "Despraza o capuchón á dereita, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_spin_clockwise": "Xira a bóla no sentido horario unhas cantas veces e déixaa parar.",
"ui.3d_mouse_step_spin_counterclockwise": "Xira a bóla no sentido antihorario unhas cantas veces e déixaa parar.",
"ui.3d_mouse_step_tilt_away": "Inclina a parte de arriba do capuchón lonxe de ti (o bordo máis próximo sobe), mantén a posición e despois sóltao.",
"ui.3d_mouse_step_tilt_left": "Inclina o capuchón á esquerda (bordo esquerdo abaixo), mantén a posición e despois sóltao.",
"ui.3d_mouse_step_tilt_right": "Inclina o capuchón á dereita (bordo dereito abaixo), mantén a posición e despois sóltao.",
"ui.3d_mouse_step_tilt_toward": "Inclina a parte de arriba do capuchón cara a ti (o bordo máis próximo baixa), mantén a posición e despois sóltao.",
"ui.3d_mouse_step_touch": "Apoia a man no capuchón sen empurralo.",
"ui.3d_mouse_step_twist_clockwise": "Xira o capuchón no sentido horario, mantén a posición e despois sóltao.",
"ui.3d_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantén a posición e despois sóltao.",
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
"ui.3d_mouse_view_axis": "Eixe da vista",
"ui.3d_mouse_view_off": "Desactivado",
"ui.3d_mouse_view_off_tip": "O perfil do controlador do rato 3D desactiva este eixe, así que o seu movemento non move a vista. Se escolles aquí o movemento doutro eixe, intercámbianse e ese movemento deixa de mover a vista.",
"ui.3d_mouse_view_orbit_sideways": "Orbitar á esquerda / dereita",
"ui.3d_mouse_view_orbit_up_down": "Orbitar arriba / abaixo",
"ui.3d_mouse_view_pan_sideways": "Desprazar á esquerda / dereita",
"ui.3d_mouse_view_pan_up_down": "Desprazar arriba / abaixo",
"ui.3d_mouse_view_roll": "Xirar sobre a vista",
"ui.3d_mouse_view_unmapped": "Eixe {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "Agardando movemento",
"ui.3d_mouse_wiggle": "Se tes un conectado, móveo un pouco.",
"ui.3d_sketch": "Bosquexo 3D",
"ui.activate": "Activar",
"ui.active_component": "Compoñente activo",
@@ -1351,6 +1460,7 @@
"ui.blocked": "Bloqueado",
"ui.bodies": "Corpos",
"ui.bodies_all_if_none": "Corpos (todos se non hai ningún)",
"ui.bottom": "Inferior",
"ui.box": "Caixa",
"ui.break": "Romper",
"ui.browser_bodies_one": "{count} corpo",
@@ -1503,6 +1613,7 @@
"ui.grid_follows_view_hint": "As vistas laterais mostran a grella no plano vertical que ten diante en vez do chan",
"ui.ground_shadow": "Sombra no chan",
"ui.groupSelected": "Agrupar a selección",
"ui.hatch": "Raiado",
"ui.heading_add_ins": "COMPLEMENTOS",
"ui.heading_assemble": "ENSAMBLAR",
"ui.heading_box": "CAIXA",
@@ -1793,59 +1904,6 @@
"ui.smooth_wheel_zoom": "Zoom suave coa roda",
"ui.smooth_wheel_zoom_tip": "Desactívao para aplicar cada movemento da roda de inmediato, sen suavizado",
"ui.snap": "Axuste",
"ui.space_mouse_axes_reassigned": "Os eixes están reasignados en Preferencias, e iso segue aplicándose sobre esta calibración.",
"ui.space_mouse_calibrate": "Calibrar…",
"ui.space_mouse_calibrate_tip": "Move o capuchón como pide a xanela para que SolidCraft aprenda cara a onde e ata onde vai cada eixe deste rato 3D. Gárdase para este rato 3D.",
"ui.space_mouse_calibrate_title": "Calibrar o rato 3D",
"ui.space_mouse_calibrated": "Calibrado",
"ui.space_mouse_calibration": "Calibración do rato 3D",
"ui.space_mouse_calibration_done": "Feito. Gárdaa para usala sempre que este rato 3D estea conectado.",
"ui.space_mouse_calibration_no_response": "Un eixe case non se moveu, así que non se pode calibrar. Volve comezar e move o capuchón máis lonxe cada vez.",
"ui.space_mouse_dead_zone": "Zona morta",
"ui.space_mouse_dead_zone_tip": "Canto ten que moverse o capuchón antes de que se mova a vista, como parte do seu percorrido completo. Auméntaa se a vista se despraza co capuchón en repouso.",
"ui.space_mouse_driven_by": "Que movemento calibrado do dispositivo controla este eixe. Se escolles un que xa usa outro eixe, intercámbianse.",
"ui.space_mouse_driven_by_label": "controlado por",
"ui.space_mouse_driver": "Controlador do rato 3D",
"ui.space_mouse_driver_auto": "Automático",
"ui.space_mouse_driver_direct": "Directo (hidraw)",
"ui.space_mouse_driver_only_direct": "Lectura directa do dispositivo; spacenavd non está en execución",
"ui.space_mouse_driver_only_spacenavd": "Lectura mediante spacenavd, a única opción dispoñible",
"ui.space_mouse_forget": "Esquecer a calibración",
"ui.space_mouse_hint": "Move e rota a vista á vez; os bosquexos só usan desprazamento e zoom.",
"ui.space_mouse_motion_rotation_x": "Rotación X",
"ui.space_mouse_motion_rotation_y": "Rotación Y",
"ui.space_mouse_motion_rotation_z": "Rotación Z",
"ui.space_mouse_motion_translation_x": "Translación X",
"ui.space_mouse_motion_translation_y": "Translación Y",
"ui.space_mouse_motion_translation_z": "Translación Z",
"ui.space_mouse_navigate": "Navegar con SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Non se atopou ningún rato 3D",
"ui.space_mouse_not_calibrated": "Sen calibrar",
"ui.space_mouse_reading": "Lectura",
"ui.space_mouse_reset_axes": "Restablecer asignación de eixes",
"ui.space_mouse_step_lift": "Levanta o capuchón, mantéño e sóltao.",
"ui.space_mouse_step_press": "Preme o capuchón cara abaixo, mantéño e sóltao.",
"ui.space_mouse_step_pull": "Tira do capuchón cara a ti, mantéño e sóltao.",
"ui.space_mouse_step_push": "Empurra o capuchón lonxe de ti, mantéño e sóltao.",
"ui.space_mouse_step_rest": "Solta o capuchón e déixao quieto.",
"ui.space_mouse_step_slide_left": "Despraza o capuchón á esquerda, mantéño e sóltao.",
"ui.space_mouse_step_slide_right": "Despraza o capuchón á dereita, mantéño e sóltao.",
"ui.space_mouse_step_tilt_back": "Inclina o capuchón cara atrás (bordo dianteiro arriba), mantéño e sóltao.",
"ui.space_mouse_step_tilt_forward": "Inclina o capuchón cara adiante (bordo dianteiro abaixo), mantéño e sóltao.",
"ui.space_mouse_step_tilt_left": "Inclina o capuchón á esquerda (bordo esquerdo abaixo), mantéño e sóltao.",
"ui.space_mouse_step_tilt_right": "Inclina o capuchón á dereita (bordo dereito abaixo), mantéño e sóltao.",
"ui.space_mouse_step_twist_clockwise": "Xira o capuchón no sentido horario, mantéño e sóltao.",
"ui.space_mouse_step_twist_counterclockwise": "Xira o capuchón no sentido antihorario, mantéño e sóltao.",
"ui.space_mouse_view_axis": "Eixe da vista",
"ui.space_mouse_view_off": "Desactivado",
"ui.space_mouse_view_off_tip": "O perfil do controlador do rato 3D desactiva este eixe, así que o seu movemento non move a vista. Se escolles aquí o movemento doutro eixe, intercámbianse e ese movemento deixa de mover a vista.",
"ui.space_mouse_view_orbit_sideways": "Orbitar á esquerda / dereita",
"ui.space_mouse_view_orbit_up_down": "Orbitar arriba / abaixo",
"ui.space_mouse_view_pan_sideways": "Desprazar á esquerda / dereita",
"ui.space_mouse_view_pan_up_down": "Desprazar arriba / abaixo",
"ui.space_mouse_view_roll": "Xirar sobre a vista",
"ui.space_mouse_view_unmapped": "Eixe {n}",
"ui.space_mouse_view_zoom": "Zoom",
"ui.spacing": "Espazado",
"ui.spacing_2": "Espazado 2",
"ui.sphere": "Esfera",
+54 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Válasszon…",
"Circular": "Kör alakú",
"Circumference": "Kerület",
"Clockwise": "Óramutató járásával megegyező",
"Close draft": "Vázlat bezárása",
"Collisions": "Ütközések",
"Colour": "Szín",
@@ -321,6 +322,7 @@
"Search commands": "Parancsok keresése",
"Search this library": "Keresés ebben a könyvtárban",
"Section": "Metszet",
"Section position": "Szelvény helyzete",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Más kijelölése: tartsa nyomva a bal gombot egy helyen a modellen (vagy jobb kattintás › Más kijelölése…) a kurzor alatti elemek listázásához; kattintson újra ugyanarra a helyre a következő elem kijelöléséhez. A középső gomb dupla kattintása a kezdőnézetre vált (Beállítások › Navigáció).",
"Select bodies (a face picks its body)": "Testek kijelölése (egy lap a testét jelöli ki)",
"Select bodies or faces instead": "Inkább testek vagy lapok kijelölése",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "A SolidCraft bezárult, mielőtt ezek a tervek mentve lettek volna. Az utolsó automatikus mentésük megnyitható.",
"Specialise": "Specializálás",
"Speed": "Sebesség",
"Square": "Négyzet",
"Stand it up": "Felállítás",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Felállítás: negyedfordulat az X körül, egyetlen visszavonható Mozgatás elemként.",
"Stand this part up?": "Felállítja ezt az alkatrészt?",
"Start angle": "Kezdőszög",
"Start bulge": "Kezdő ívelés",
"Start continuity": "Kezdő folytonosság",
"Step": "Lépés",
@@ -411,6 +415,7 @@
"angle, height": "szög, magasság",
"appearance.dielectric_ior": "Dielektromos törésmutató",
"appearance.metallic": "Fémes",
"appearance.only_available": "Csak elérhető megjelenések",
"appearance.pbr_help": "Tekintse meg ezt a felületet Renderelésben vagy termék-PNG-ben. Nincs fénytörés vagy áteresztés.",
"appearance.pbr_opaque": "Átlátszatlan PBR",
"appearance.roughness": "Érdesség",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "STEP beszúrása",
"cmd.file_save_mesh": "Mentés hálóként",
"cmd.file_slicers": "Telepített szeletelők",
"cmd.frame_member": "Szerkezeti elem",
"cmd.frame_profile_insert": "Profil beszúrása",
"cmd.frame_profile_list": "Profilkönyvtár",
"cmd.frame_profile_properties": "Profil tulajdonságai",
"cmd.inspect_accessibility": "Hozzáférhetőség-elemzés",
"cmd.inspect_center_of_mass": "Tömegközéppont",
"cmd.inspect_clearance": "Hézag",
@@ -568,6 +577,7 @@
"cmd.material_create": "Új fizikai anyag",
"cmd.material_delete": "Fizikai anyag törlése",
"cmd.material_edit": "Fizikai anyag szerkesztése",
"cmd.material_inspect": "Fizikai anyag vizsgálata",
"cmd.material_list": "Anyagok listázása",
"cmd.material_properties_directional": "Irányfüggő rugalmassági modulus",
"cmd.material_properties_validate": "Fizikai anyagadatok ellenőrzése",
@@ -1120,7 +1130,50 @@
"trace.stopped_by_contact": "Vége a(z) {sample}. mintánál: {a} áthatolna ezen: {b}",
"trace.stopped_by_loop": "Vége a(z) {sample}. mintánál: a(z) {joint} csuklón átmenő hurok nem zárható tovább",
"ui.1_degree_of_freedom": "1 szabadságfok",
"ui.3d_mouse_calibrate": "Kalibrálás…",
"ui.3d_mouse_calibrate_tip": "Mozgassa a kupakot az ablak kérése szerint, hogy a SolidCraft megtanulja, merre és mennyire mozdul a 3D-egér mindegyik tengelye. Ehhez a 3D-egérhez mentve.",
"ui.3d_mouse_calibrate_title": "3D-egér kalibrálása",
"ui.3d_mouse_calibrate_wait": "Mozdítsa meg egyszer a 3D-egeret, hogy a SolidCraft lássa.",
"ui.3d_mouse_calibrated": "Kalibrálva",
"ui.3d_mouse_calibration": "3D-egér kalibrálása",
"ui.3d_mouse_calibration_done": "Kész. Mentse, hogy a 3D-egér csatlakoztatásakor mindig használatos legyen.",
"ui.3d_mouse_calibration_no_response": "Az egyik tengely alig mozdult, így nem kalibrálható. Kezdje elölről, és minden alkalommal mozgassa a kupakot messzebb.",
"ui.3d_mouse_circles_help": "A körök azt mutatják, mit küld az eszköz kalibrálás után. A tesztmodell azt mutatja, mit tesz ezzel a nézet a megfordítás, sebesség és tengelykiosztás után.",
"ui.3d_mouse_dead_zone": "Holtsáv",
"ui.3d_mouse_dead_zone_tip": "Mennyit kell mozdítani a kupakot, mielőtt a nézet mozdulna, a teljes elmozdulás arányában. Növelje, ha a nézet sodródik, miközben a kupak nyugalomban van.",
"ui.3d_mouse_denied": "A SolidCraft látja a 3D-egeret, de nem olvashatja. Az udev-szabályt a 3D-egér beállítási jegyzetei tartalmazzák.",
"ui.3d_mouse_denied_short": "Megvan, de nem olvasható",
"ui.3d_mouse_driver_fallback": "Egyelőre a spacenavd-n át olvassa: magát az eszközt nem sikerült megnyitni.",
"ui.3d_mouse_forget": "Kalibrálás elfelejtése",
"ui.3d_mouse_ghost_left_viewport": "Bal nézet",
"ui.3d_mouse_ghost_left_window": "Bal ablak",
"ui.3d_mouse_ghost_position": "Helyzet",
"ui.3d_mouse_ghost_right_viewport": "Jobb nézet",
"ui.3d_mouse_hint": "A nézetet együtt mozgatja és forgatja; a vázlatok csak pásztázást és nagyítást használnak.",
"ui.3d_mouse_navigate": "Navigálás a 3D-egérrel",
"ui.3d_mouse_none": "Nem található 3D-egér",
"ui.3d_mouse_not_calibrated": "Nincs kalibrálva",
"ui.3d_mouse_offer": "A 3D-egér kalibrálása kijavítja a felcserélt vagy megfordított tengelyeket, és beállítja a sebességét és az érzetét.",
"ui.3d_mouse_offer_never": "Ne jelenjen meg többé",
"ui.3d_mouse_offer_swap": "Váltás Hidrawra (közvetlen illesztőprogram)",
"ui.3d_mouse_reading": "Beolvasás",
"ui.3d_mouse_reset_view": "Nézet alaphelyzetbe állítása",
"ui.3d_mouse_restore": "3D-egér alapértékeinek visszaállítása",
"ui.3d_mouse_restore_ask": "Visszaállítja a 3D-egér beállításait az alapértékekre? A kalibrálás és a gombkiosztás megmarad.",
"ui.3d_mouse_speed": "3D-egér sebessége",
"ui.3d_mouse_step_lift": "Emelje fel a kupakot, tartsa, majd engedje el.",
"ui.3d_mouse_step_press": "Nyomja le a kupakot, tartsa, majd engedje el.",
"ui.3d_mouse_step_pull": "Húzza a kupakot maga felé, tartsa, majd engedje el.",
"ui.3d_mouse_step_push": "Tolja a kupakot magától el, tartsa, majd engedje el.",
"ui.3d_mouse_step_rest": "Vegye le a kezét a kupakról, és hagyja nyugodtan.",
"ui.3d_mouse_step_slide_left": "Csúsztassa a kupakot balra, tartsa, majd engedje el.",
"ui.3d_mouse_step_slide_right": "Csúsztassa a kupakot jobbra, tartsa, majd engedje el.",
"ui.3d_mouse_step_tilt_left": "Billentse balra a kupakot (a bal él lefelé), tartsa, majd engedje el.",
"ui.3d_mouse_step_tilt_right": "Billentse jobbra a kupakot (a jobb él lefelé), tartsa, majd engedje el.",
"ui.3d_mouse_step_twist_clockwise": "Csavarja el a kupakot az óramutató járásával megegyezően, tartsa, majd engedje el.",
"ui.3d_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
"ui.3d_mouse_waiting": "Mozgásra vár",
"ui.3d_mouse_wiggle": "Ha van csatlakoztatva egy, mozgassa meg egy kicsit.",
"ui.3d_sketch": "3D vázlat",
"ui.activate": "Aktiválás",
"ui.active_component": "Aktív komponens",
@@ -1502,6 +1555,7 @@
"ui.grid_follows_view_hint": "Az oldalnézetek a talaj helyett a feléjük néző függőleges síkon mutatják a rácsot",
"ui.ground_shadow": "Talajárnyék",
"ui.groupSelected": "Kijelöltek csoportosítása",
"ui.hatch": "Sraffozás",
"ui.heading_add_ins": "BŐVÍTMÉNYEK",
"ui.heading_assemble": "ÖSSZESZERELÉS",
"ui.heading_box": "DOBOZ",
@@ -1792,34 +1846,6 @@
"ui.smooth_wheel_zoom": "Sima görgőnagyítás",
"ui.smooth_wheel_zoom_tip": "Kapcsolja ki az egyes görgőesemények azonnali alkalmazásához, kifutás nélkül",
"ui.snap": "Illesztés",
"ui.space_mouse_calibrate": "Kalibrálás…",
"ui.space_mouse_calibrate_tip": "Mozgassa a kupakot az ablak kérése szerint, hogy a SolidCraft megtanulja, merre és mennyire mozdul a 3D-egér mindegyik tengelye. Ehhez a 3D-egérhez mentve.",
"ui.space_mouse_calibrate_title": "3D-egér kalibrálása",
"ui.space_mouse_calibrated": "Kalibrálva",
"ui.space_mouse_calibration": "3D-egér kalibrálása",
"ui.space_mouse_calibration_done": "Kész. Mentse, hogy a 3D-egér csatlakoztatásakor mindig használatos legyen.",
"ui.space_mouse_calibration_no_response": "Az egyik tengely alig mozdult, így nem kalibrálható. Kezdje elölről, és minden alkalommal mozgassa a kupakot messzebb.",
"ui.space_mouse_dead_zone": "Holtsáv",
"ui.space_mouse_dead_zone_tip": "Mennyit kell mozdítani a kupakot, mielőtt a nézet mozdulna, a teljes elmozdulás arányában. Növelje, ha a nézet sodródik, miközben a kupak nyugalomban van.",
"ui.space_mouse_forget": "Kalibrálás elfelejtése",
"ui.space_mouse_hint": "A nézetet együtt mozgatja és forgatja; a vázlatok csak pásztázást és nagyítást használnak.",
"ui.space_mouse_navigate": "Navigálás SpaceNavigator / SpaceMouse eszközzel",
"ui.space_mouse_none": "Nem található 3D-egér",
"ui.space_mouse_not_calibrated": "Nincs kalibrálva",
"ui.space_mouse_reading": "Beolvasás",
"ui.space_mouse_step_lift": "Emelje fel a kupakot, tartsa, majd engedje el.",
"ui.space_mouse_step_press": "Nyomja le a kupakot, tartsa, majd engedje el.",
"ui.space_mouse_step_pull": "Húzza a kupakot maga felé, tartsa, majd engedje el.",
"ui.space_mouse_step_push": "Tolja a kupakot magától el, tartsa, majd engedje el.",
"ui.space_mouse_step_rest": "Vegye le a kezét a kupakról, és hagyja nyugodtan.",
"ui.space_mouse_step_slide_left": "Csúsztassa a kupakot balra, tartsa, majd engedje el.",
"ui.space_mouse_step_slide_right": "Csúsztassa a kupakot jobbra, tartsa, majd engedje el.",
"ui.space_mouse_step_tilt_back": "Billentse hátra a kupakot (az elülső él felfelé), tartsa, majd engedje el.",
"ui.space_mouse_step_tilt_forward": "Billentse előre a kupakot (az elülső él lefelé), tartsa, majd engedje el.",
"ui.space_mouse_step_tilt_left": "Billentse balra a kupakot (a bal él lefelé), tartsa, majd engedje el.",
"ui.space_mouse_step_tilt_right": "Billentse jobbra a kupakot (a jobb él lefelé), tartsa, majd engedje el.",
"ui.space_mouse_step_twist_clockwise": "Csavarja el a kupakot az óramutató járásával megegyezően, tartsa, majd engedje el.",
"ui.space_mouse_step_twist_counterclockwise": "Csavarja el a kupakot az óramutató járásával ellentétesen, tartsa, majd engedje el.",
"ui.spacing": "Térköz",
"ui.spacing_2": "2. térköz",
"ui.sphere": "Gömb",
+54 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Pilih…",
"Circular": "Melingkar",
"Circumference": "Keliling",
"Clockwise": "Searah jarum jam",
"Close draft": "Tutup draf",
"Collisions": "Tabrakan",
"Colour": "Warna",
@@ -321,6 +322,7 @@
"Search commands": "Cari perintah",
"Search this library": "Cari di pustaka ini",
"Section": "Penampang",
"Section position": "Posisi penampang",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Select Other: tahan tombol kiri diam di model (atau klik kanan › Select Other…) untuk mendaftar semua yang ada di bawah kursor; klik lagi di titik yang sama untuk memilih benda berikutnya. Klik dua kali tombol tengah untuk ke Home (Preferensi › Navigasi).",
"Select bodies (a face picks its body)": "Pilih bodi (memilih muka berarti memilih bodinya)",
"Select bodies or faces instead": "Pilih bodi atau muka saja",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft tertutup sebelum desain ini disimpan. Simpan otomatis terakhirnya dapat dibuka.",
"Specialise": "Spesialisasikan",
"Speed": "Kecepatan",
"Square": "Persegi",
"Stand it up": "Tegakkan",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Tegakkan: putaran seperempat terhadap X, sebagai satu fitur Move yang dapat dibatalkan.",
"Stand this part up?": "Tegakkan part ini?",
"Start angle": "Sudut awal",
"Start bulge": "Bulge awal",
"Start continuity": "Kontinuitas awal",
"Step": "Langkah",
@@ -411,6 +415,7 @@
"angle, height": "sudut, tinggi",
"appearance.dielectric_ior": "IOR Dielektrik",
"appearance.metallic": "Metalik",
"appearance.only_available": "Hanya tampilan yang tersedia",
"appearance.pbr_help": "Lihat permukaan ini di Render atau PNG produk. Tanpa refraksi atau transmisi.",
"appearance.pbr_opaque": "PBR Opak",
"appearance.roughness": "Kekasaran",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "Sisipkan STEP",
"cmd.file_save_mesh": "Simpan Sebagai Mesh",
"cmd.file_slicers": "Slicer Terpasang",
"cmd.frame_member": "Batang Rangka",
"cmd.frame_profile_insert": "Sisipkan Profil",
"cmd.frame_profile_list": "Pustaka Profil",
"cmd.frame_profile_properties": "Properti Profil",
"cmd.inspect_accessibility": "Analisis Aksesibilitas",
"cmd.inspect_center_of_mass": "Pusat Massa",
"cmd.inspect_clearance": "Kelonggaran",
@@ -568,6 +577,7 @@
"cmd.material_create": "Material Fisik Baru",
"cmd.material_delete": "Hapus Material Fisik",
"cmd.material_edit": "Edit Material Fisik",
"cmd.material_inspect": "Periksa Material Fisik",
"cmd.material_list": "Daftar Material",
"cmd.material_properties_directional": "Modulus Elastis Arah",
"cmd.material_properties_validate": "Validasi Data Material Fisik",
@@ -1120,7 +1130,50 @@
"trace.stopped_by_contact": "Berakhir di sampel {sample}: {a} akan menembus {b}",
"trace.stopped_by_loop": "Berakhir di sampel {sample}: loop melalui {joint} tidak dapat menutup lebih jauh",
"ui.1_degree_of_freedom": "1 derajat kebebasan",
"ui.3d_mouse_calibrate": "Kalibrasi…",
"ui.3d_mouse_calibrate_tip": "Gerakkan cap sesuai permintaan jendela, agar SolidCraft mempelajari arah dan jarak setiap sumbu mouse 3D ini. Disimpan untuk mouse 3D ini.",
"ui.3d_mouse_calibrate_title": "Kalibrasi mouse 3D",
"ui.3d_mouse_calibrate_wait": "Gerakkan mouse 3D sekali agar SolidCraft dapat melihatnya.",
"ui.3d_mouse_calibrated": "Terkalibrasi",
"ui.3d_mouse_calibration": "Kalibrasi mouse 3D",
"ui.3d_mouse_calibration_done": "Selesai. Simpan untuk dipakai setiap kali mouse 3D ini terhubung.",
"ui.3d_mouse_calibration_no_response": "Satu sumbu hampir tidak bergerak, sehingga tidak dapat dikalibrasi. Mulai ulang dan gerakkan cap lebih jauh setiap kali.",
"ui.3d_mouse_circles_help": "Lingkaran menunjukkan apa yang dikirim perangkat setelah kalibrasi. Model uji menunjukkan apa yang dilakukan tampilan dengannya, setelah pembalikan, kecepatan, dan penetapan sumbu.",
"ui.3d_mouse_dead_zone": "Zona mati",
"ui.3d_mouse_dead_zone_tip": "Seberapa jauh cap harus bergerak sebelum tampilan bergerak, sebagai bagian dari total jangkauannya. Naikkan jika tampilan bergeser saat cap diam.",
"ui.3d_mouse_denied": "SolidCraft melihat mouse 3D tetapi tidak diizinkan membacanya. Lihat catatan penyiapan mouse 3D untuk aturan udev.",
"ui.3d_mouse_denied_short": "Ditemukan, tetapi tidak bisa dibaca",
"ui.3d_mouse_driver_fallback": "Sementara dibaca lewat spacenavd: perangkatnya sendiri tidak dapat dibuka.",
"ui.3d_mouse_forget": "Lupakan kalibrasi",
"ui.3d_mouse_ghost_left_viewport": "Viewport kiri",
"ui.3d_mouse_ghost_left_window": "Jendela kiri",
"ui.3d_mouse_ghost_position": "Posisi",
"ui.3d_mouse_ghost_right_viewport": "Viewport kanan",
"ui.3d_mouse_hint": "Gerakkan dan putar tampilan bersama; sketsa hanya memakai pan dan zoom.",
"ui.3d_mouse_navigate": "Navigasi dengan mouse 3D",
"ui.3d_mouse_none": "Mouse 3D tidak ditemukan",
"ui.3d_mouse_not_calibrated": "Belum dikalibrasi",
"ui.3d_mouse_offer": "Mengkalibrasi mouse 3D memperbaiki sumbu yang tertukar atau terbalik dan mengatur kecepatan serta rasanya.",
"ui.3d_mouse_offer_never": "Jangan tampilkan lagi",
"ui.3d_mouse_offer_swap": "Beralih ke Hidraw (driver langsung)",
"ui.3d_mouse_reading": "Pembacaan",
"ui.3d_mouse_reset_view": "Atur ulang tampilan",
"ui.3d_mouse_restore": "Pulihkan bawaan mouse 3D",
"ui.3d_mouse_restore_ask": "Kembalikan pengaturan mouse 3D ke bawaan? Kalibrasi dan pemetaan tombol tetap ada.",
"ui.3d_mouse_speed": "Kecepatan mouse 3D",
"ui.3d_mouse_step_lift": "Angkat cap ke atas, tahan, lalu lepaskan.",
"ui.3d_mouse_step_press": "Tekan cap ke bawah, tahan, lalu lepaskan.",
"ui.3d_mouse_step_pull": "Tarik cap ke arah Anda, tahan, lalu lepaskan.",
"ui.3d_mouse_step_push": "Dorong cap menjauh dari Anda, tahan, lalu lepaskan.",
"ui.3d_mouse_step_rest": "Lepaskan tangan dari cap dan biarkan diam.",
"ui.3d_mouse_step_slide_left": "Geser cap ke kiri, tahan, lalu lepaskan.",
"ui.3d_mouse_step_slide_right": "Geser cap ke kanan, tahan, lalu lepaskan.",
"ui.3d_mouse_step_tilt_left": "Miringkan cap ke kiri (tepi kiri turun), tahan, lalu lepaskan.",
"ui.3d_mouse_step_tilt_right": "Miringkan cap ke kanan (tepi kanan turun), tahan, lalu lepaskan.",
"ui.3d_mouse_step_twist_clockwise": "Putar cap searah jarum jam, tahan, lalu lepaskan.",
"ui.3d_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
"ui.3d_mouse_waiting": "Menunggu gerakan",
"ui.3d_mouse_wiggle": "Jika Anda memasang satu, goyangkan sedikit.",
"ui.3d_sketch": "Sketsa 3D",
"ui.activate": "Aktifkan",
"ui.active_component": "Komponen aktif",
@@ -1502,6 +1555,7 @@
"ui.grid_follows_view_hint": "Tampilan samping menampilkan grid pada bidang tegak yang menghadapnya, bukan pada dasar",
"ui.ground_shadow": "Bayangan dasar",
"ui.groupSelected": "Kelompokkan yang Dipilih",
"ui.hatch": "Arsir",
"ui.heading_add_ins": "ADD-IN",
"ui.heading_assemble": "RAKIT",
"ui.heading_box": "KOTAK",
@@ -1792,34 +1846,6 @@
"ui.smooth_wheel_zoom": "Zoom roda halus",
"ui.smooth_wheel_zoom_tip": "Matikan untuk menerapkan setiap peristiwa roda segera, tanpa ekor penghalusan",
"ui.snap": "Snap",
"ui.space_mouse_calibrate": "Kalibrasi…",
"ui.space_mouse_calibrate_tip": "Gerakkan cap sesuai permintaan jendela, agar SolidCraft mempelajari arah dan jarak setiap sumbu mouse 3D ini. Disimpan untuk mouse 3D ini.",
"ui.space_mouse_calibrate_title": "Kalibrasi mouse 3D",
"ui.space_mouse_calibrated": "Terkalibrasi",
"ui.space_mouse_calibration": "Kalibrasi mouse 3D",
"ui.space_mouse_calibration_done": "Selesai. Simpan untuk dipakai setiap kali mouse 3D ini terhubung.",
"ui.space_mouse_calibration_no_response": "Satu sumbu hampir tidak bergerak, sehingga tidak dapat dikalibrasi. Mulai ulang dan gerakkan cap lebih jauh setiap kali.",
"ui.space_mouse_dead_zone": "Zona mati",
"ui.space_mouse_dead_zone_tip": "Seberapa jauh cap harus bergerak sebelum tampilan bergerak, sebagai bagian dari total jangkauannya. Naikkan jika tampilan bergeser saat cap diam.",
"ui.space_mouse_forget": "Lupakan kalibrasi",
"ui.space_mouse_hint": "Gerakkan dan putar tampilan bersama; sketsa hanya memakai pan dan zoom.",
"ui.space_mouse_navigate": "Navigasi dengan SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Mouse 3D tidak ditemukan",
"ui.space_mouse_not_calibrated": "Belum dikalibrasi",
"ui.space_mouse_reading": "Pembacaan",
"ui.space_mouse_step_lift": "Angkat cap ke atas, tahan, lalu lepaskan.",
"ui.space_mouse_step_press": "Tekan cap ke bawah, tahan, lalu lepaskan.",
"ui.space_mouse_step_pull": "Tarik cap ke arah Anda, tahan, lalu lepaskan.",
"ui.space_mouse_step_push": "Dorong cap menjauh dari Anda, tahan, lalu lepaskan.",
"ui.space_mouse_step_rest": "Lepaskan tangan dari cap dan biarkan diam.",
"ui.space_mouse_step_slide_left": "Geser cap ke kiri, tahan, lalu lepaskan.",
"ui.space_mouse_step_slide_right": "Geser cap ke kanan, tahan, lalu lepaskan.",
"ui.space_mouse_step_tilt_back": "Miringkan cap ke belakang (tepi depan naik), tahan, lalu lepaskan.",
"ui.space_mouse_step_tilt_forward": "Miringkan cap ke depan (tepi depan turun), tahan, lalu lepaskan.",
"ui.space_mouse_step_tilt_left": "Miringkan cap ke kiri (tepi kiri turun), tahan, lalu lepaskan.",
"ui.space_mouse_step_tilt_right": "Miringkan cap ke kanan (tepi kanan turun), tahan, lalu lepaskan.",
"ui.space_mouse_step_twist_clockwise": "Putar cap searah jarum jam, tahan, lalu lepaskan.",
"ui.space_mouse_step_twist_counterclockwise": "Putar cap berlawanan arah jarum jam, tahan, lalu lepaskan.",
"ui.spacing": "Jarak antar",
"ui.spacing_2": "Jarak antar 2",
"ui.sphere": "Bola",
+111 -53
View File
@@ -72,6 +72,7 @@
"Choose…": "Scegli…",
"Circular": "Circolare",
"Circumference": "Circonferenza",
"Clockwise": "Orario",
"Close draft": "Chiudi bozza",
"Collisions": "Collisioni",
"Colour": "Colore",
@@ -325,6 +326,7 @@
"Search commands": "Cerca comandi",
"Search this library": "Cerca in questa libreria",
"Section": "Sezione",
"Section position": "Posizione della sezione",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleziona altro: tieni premuto il pulsante sinistro senza muovere il puntatore sul modello (o fai clic destro › Seleziona altro…) per elencare gli oggetti sotto il cursore; fai di nuovo clic nello stesso punto per selezionare l'oggetto successivo. Fai doppio clic con il pulsante centrale per tornare alla vista iniziale (Preferenze › Navigazione).",
"Select bodies (a face picks its body)": "Seleziona corpi (una faccia seleziona il corpo a cui appartiene)",
"Select bodies or faces instead": "Seleziona invece corpi o facce",
@@ -348,9 +350,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft si è chiuso prima del salvataggio di questi progetti. È possibile aprire il loro ultimo salvataggio automatico.",
"Specialise": "Specializza",
"Speed": "Velocità",
"Square": "Quadrato",
"Stand it up": "Raddrizza",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Raddrizza: un quarto di giro attorno a X, come singola lavorazione Sposta annullabile.",
"Stand this part up?": "Raddrizzare questa parte?",
"Start angle": "Angolo iniziale",
"Start bulge": "Rigonfiamento iniziale",
"Start continuity": "Continuità iniziale",
"Step": "Passo",
@@ -415,6 +419,7 @@
"angle, height": "angolo, altezza",
"appearance.dielectric_ior": "Indice di rifrazione dielettrico",
"appearance.metallic": "Metallicità",
"appearance.only_available": "Solo aspetti disponibili",
"appearance.pbr_help": "Visualizza questa superficie in Rendering o in un PNG del prodotto. Senza rifrazione né trasmissione.",
"appearance.pbr_opaque": "PBR opaco",
"appearance.roughness": "Rugosità",
@@ -533,6 +538,10 @@
"cmd.file_insert_step": "Inserisci STEP",
"cmd.file_save_mesh": "Salva come mesh",
"cmd.file_slicers": "Slicer installati",
"cmd.frame_member": "Membro strutturale",
"cmd.frame_profile_insert": "Inserisci profilo",
"cmd.frame_profile_list": "Libreria profili",
"cmd.frame_profile_properties": "Proprietà del profilo",
"cmd.inspect_accessibility": "Analisi di accessibilità",
"cmd.inspect_center_of_mass": "Centro di massa",
"cmd.inspect_clearance": "Distanza libera",
@@ -572,6 +581,7 @@
"cmd.material_create": "Nuovo materiale fisico",
"cmd.material_delete": "Elimina materiale fisico",
"cmd.material_edit": "Modifica materiale fisico",
"cmd.material_inspect": "Ispeziona materiale fisico",
"cmd.material_list": "Elenca materiali",
"cmd.material_properties_directional": "Modulo elastico direzionale",
"cmd.material_properties_validate": "Convalida dati del materiale fisico",
@@ -1124,7 +1134,106 @@
"trace.stopped_by_contact": "Termina al campione {sample}: {a} attraverserebbe {b}",
"trace.stopped_by_loop": "Termina al campione {sample}: la catena chiusa che passa per {joint} non può chiudersi ulteriormente",
"ui.1_degree_of_freedom": "1 grado di libertà",
"ui.3d_mouse_axes_reassigned": "Gli assi sono riassegnati nelle Preferenze, e questo vale anche dopo questa calibrazione.",
"ui.3d_mouse_button": "Pulsante {number}",
"ui.3d_mouse_button_nothing": "Niente",
"ui.3d_mouse_button_settings": "Impostazioni del mouse 3D",
"ui.3d_mouse_buttons": "Pulsanti",
"ui.3d_mouse_buttons_hint": "Premi un pulsante del tuo mouse 3D per trovarlo qui, poi scegli cosa fa.",
"ui.3d_mouse_buttons_no_device": "Collega un mouse 3D per scegliere cosa fanno i suoi pulsanti.",
"ui.3d_mouse_buttons_reset": "Ripristina i pulsanti predefiniti",
"ui.3d_mouse_calibrate": "Calibra…",
"ui.3d_mouse_calibrate_tip": "Muovi la manopola come chiede la finestra, così SolidCraft impara in che direzione e quanto va ogni asse di questo mouse 3D. Viene salvata per questo mouse 3D.",
"ui.3d_mouse_calibrate_title": "Calibra mouse 3D",
"ui.3d_mouse_calibrate_wait": "Muovi il mouse 3D una volta così SolidCraft lo vede.",
"ui.3d_mouse_calibrated": "Calibrato",
"ui.3d_mouse_calibration": "Calibrazione del mouse 3D",
"ui.3d_mouse_calibration_done": "Fatto. Salvala per usarla ogni volta che questo mouse 3D è collegato.",
"ui.3d_mouse_calibration_no_response": "Un asse si è mosso appena, quindi non si può calibrare. Ricomincia e muovi la manopola più a fondo ogni volta.",
"ui.3d_mouse_circles_help": "I cerchi mostrano ciò che il dispositivo invia dopo la calibrazione. Il modello di prova mostra cosa ne fa la vista, dopo inversione, velocità e assegnazione degli assi.",
"ui.3d_mouse_dead_zone": "Zona morta",
"ui.3d_mouse_dead_zone_tip": "Quanto deve muoversi la manopola prima che si muova la vista, come quota della sua corsa completa. Aumentala se la vista si sposta con la manopola a riposo.",
"ui.3d_mouse_denied": "SolidCraft vede un mouse 3D ma non ha il permesso di leggerlo. La regola udev è nelle note di configurazione del mouse 3D.",
"ui.3d_mouse_denied_short": "Trovato, ma non leggibile",
"ui.3d_mouse_driven_by": "Quale movimento calibrato del dispositivo comanda questo asse. Se ne scegli uno già usato da un altro asse, i due si scambiano.",
"ui.3d_mouse_driven_by_label": "comandato da",
"ui.3d_mouse_driver": "Driver del mouse 3D",
"ui.3d_mouse_driver_auto": "Automatico",
"ui.3d_mouse_driver_direct": "Diretto (hidraw)",
"ui.3d_mouse_driver_fallback": "Per ora legge tramite spacenavd: il dispositivo stesso non si è potuto aprire.",
"ui.3d_mouse_driver_only_direct": "Lettura diretta del dispositivo; spacenavd non è in esecuzione",
"ui.3d_mouse_driver_only_spacenavd": "Lettura tramite spacenavd, l'unica possibilità disponibile",
"ui.3d_mouse_forget": "Dimentica calibrazione",
"ui.3d_mouse_ghost_left_viewport": "Vista a sinistra",
"ui.3d_mouse_ghost_left_window": "Finestra a sinistra",
"ui.3d_mouse_ghost_position": "Posizione",
"ui.3d_mouse_ghost_right_viewport": "Vista a destra",
"ui.3d_mouse_hint": "Sposta e ruota la vista insieme; gli schizzi usano solo panoramica e zoom.",
"ui.3d_mouse_kind_cap": "Ritorno al centro",
"ui.3d_mouse_kind_question": "Che tipo di mouse 3D è?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "Il valore resta finché tieni, come in un dispositivo di tipo «Ritorno al centro».",
"ui.3d_mouse_looks_like_trackball": "Il valore torna indietro appena smetti di muovere, come in una trackball.",
"ui.3d_mouse_motion_rotation_x": "Rotazione X",
"ui.3d_mouse_motion_rotation_y": "Rotazione Y",
"ui.3d_mouse_motion_rotation_z": "Rotazione Z",
"ui.3d_mouse_motion_translation_x": "Traslazione X",
"ui.3d_mouse_motion_translation_y": "Traslazione Y",
"ui.3d_mouse_motion_translation_z": "Traslazione Z",
"ui.3d_mouse_navigate": "Naviga con il mouse 3D",
"ui.3d_mouse_none": "Nessun mouse 3D trovato",
"ui.3d_mouse_not_calibrated": "Non calibrato",
"ui.3d_mouse_offer": "Calibrare il mouse 3D corregge gli assi scambiati o invertiti e imposta velocità e sensibilità.",
"ui.3d_mouse_offer_never": "Non mostrare più",
"ui.3d_mouse_offer_swap": "Passa a Hidraw (driver diretto)",
"ui.3d_mouse_reading": "Lettura",
"ui.3d_mouse_reset_axes": "Ripristina assegnazione assi",
"ui.3d_mouse_reset_view": "Ripristina vista",
"ui.3d_mouse_restore": "Ripristina impostazioni mouse 3D",
"ui.3d_mouse_restore_ask": "Riportare le impostazioni del mouse 3D ai valori predefiniti? Calibrazione e assegnazione dei pulsanti restano.",
"ui.3d_mouse_section": "Mouse 3D",
"ui.3d_mouse_show_in_view": "Mostra il mouse 3D nella vista 3D",
"ui.3d_mouse_show_in_view_tip": "Un'immagine del tuo mouse 3D nell'angolo in basso a sinistra, che si muove quando lo muovi.",
"ui.3d_mouse_skip": "Il mio dispositivo non può farlo",
"ui.3d_mouse_skip_tip": "Salta questo movimento in entrambi i versi; la calibrazione lo esclude.",
"ui.3d_mouse_speed": "Velocità del mouse 3D",
"ui.3d_mouse_step_lift": "Solleva la manopola, tienila, poi lasciala.",
"ui.3d_mouse_step_press": "Premi la manopola verso il basso, tienila, poi lasciala.",
"ui.3d_mouse_step_pull": "Tira la manopola verso di te, tienila, poi lasciala.",
"ui.3d_mouse_step_push": "Spingi la manopola lontano da te, tienila, poi lasciala.",
"ui.3d_mouse_step_rest": "Togli la mano dalla manopola e lasciala ferma.",
"ui.3d_mouse_step_rest_trackball": "Togli la mano dalla sfera e lasciala ferma.",
"ui.3d_mouse_step_ring_clockwise": "Gira la ghiera di scorrimento di qualche scatto in senso orario.",
"ui.3d_mouse_step_ring_counterclockwise": "Gira la ghiera di scorrimento di qualche scatto in senso antiorario.",
"ui.3d_mouse_step_roll_away": "Fai rotolare la sfera lontano da te qualche volta, poi lasciala fermare.",
"ui.3d_mouse_step_roll_left": "Fai rotolare la sfera verso sinistra qualche volta, poi lasciala fermare.",
"ui.3d_mouse_step_roll_right": "Fai rotolare la sfera verso destra qualche volta, poi lasciala fermare.",
"ui.3d_mouse_step_roll_toward": "Fai rotolare la sfera verso di te qualche volta, poi lasciala fermare.",
"ui.3d_mouse_step_slide_left": "Fai scorrere la manopola a sinistra, tienila, poi lasciala.",
"ui.3d_mouse_step_slide_right": "Fai scorrere la manopola a destra, tienila, poi lasciala.",
"ui.3d_mouse_step_spin_clockwise": "Ruota la sfera in senso orario qualche volta, poi lasciala fermare.",
"ui.3d_mouse_step_spin_counterclockwise": "Ruota la sfera in senso antiorario qualche volta, poi lasciala fermare.",
"ui.3d_mouse_step_tilt_away": "Inclina la parte superiore della manopola lontano da te (il bordo più vicino sale), tienila, poi lasciala.",
"ui.3d_mouse_step_tilt_left": "Inclina la manopola a sinistra (bordo sinistro in basso), tienila, poi lasciala.",
"ui.3d_mouse_step_tilt_right": "Inclina la manopola a destra (bordo destro in basso), tienila, poi lasciala.",
"ui.3d_mouse_step_tilt_toward": "Inclina la parte superiore della manopola verso di te (il bordo più vicino scende), tienila, poi lasciala.",
"ui.3d_mouse_step_touch": "Appoggia la mano sulla manopola senza spingerla.",
"ui.3d_mouse_step_twist_clockwise": "Ruota la manopola in senso orario, tienila, poi lasciala.",
"ui.3d_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
"ui.3d_mouse_switch_cap": "Calibra come ritorno al centro",
"ui.3d_mouse_switch_trackball": "Calibra come trackball",
"ui.3d_mouse_view_axis": "Asse della vista",
"ui.3d_mouse_view_off": "Disattivato",
"ui.3d_mouse_view_off_tip": "Il profilo del driver del mouse 3D disattiva questo asse, quindi il suo movimento non muove la vista. Se qui scegli il movimento di un altro asse, i due si scambiano e quel movimento smette di muovere la vista.",
"ui.3d_mouse_view_orbit_sideways": "Orbita sinistra / destra",
"ui.3d_mouse_view_orbit_up_down": "Orbita su / giù",
"ui.3d_mouse_view_pan_sideways": "Panoramica sinistra / destra",
"ui.3d_mouse_view_pan_up_down": "Panoramica su / giù",
"ui.3d_mouse_view_roll": "Rollio",
"ui.3d_mouse_view_unmapped": "Asse {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "In attesa di movimento",
"ui.3d_mouse_wiggle": "Se ne hai uno collegato, muovilo un po'.",
"ui.3d_sketch": "Schizzo 3D",
"ui.activate": "Attiva",
"ui.active_component": "Componente attivo",
@@ -1354,6 +1463,7 @@
"ui.blocked": "Bloccato",
"ui.bodies": "Corpi",
"ui.bodies_all_if_none": "Corpi (tutti se nessuno)",
"ui.bottom": "Inferiore",
"ui.box": "Parallelepipedo",
"ui.break": "Spezza",
"ui.browser_bodies_one": "{count} corpo",
@@ -1506,6 +1616,7 @@
"ui.grid_follows_view_hint": "Le viste laterali mostrano la griglia sul piano verticale rivolto verso di esse anziché sul piano di appoggio",
"ui.ground_shadow": "Ombra sul piano di appoggio",
"ui.groupSelected": "Raggruppa selezionati",
"ui.hatch": "Tratteggio",
"ui.heading_add_ins": "COMPONENTI AGGIUNTIVI",
"ui.heading_assemble": "ASSEMBLA",
"ui.heading_box": "PARALLELEPIPEDO",
@@ -1796,59 +1907,6 @@
"ui.smooth_wheel_zoom": "Zoom fluido con rotellina",
"ui.smooth_wheel_zoom_tip": "Disattiva per applicare immediatamente ogni evento della rotellina, senza coda di interpolazione",
"ui.snap": "Aggancio",
"ui.space_mouse_axes_reassigned": "Gli assi sono riassegnati nelle Preferenze, e questo vale anche dopo questa calibrazione.",
"ui.space_mouse_calibrate": "Calibra…",
"ui.space_mouse_calibrate_tip": "Muovi la manopola come chiede la finestra, così SolidCraft impara in che direzione e quanto va ogni asse di questo mouse 3D. Viene salvata per questo mouse 3D.",
"ui.space_mouse_calibrate_title": "Calibra mouse 3D",
"ui.space_mouse_calibrated": "Calibrato",
"ui.space_mouse_calibration": "Calibrazione del mouse 3D",
"ui.space_mouse_calibration_done": "Fatto. Salvala per usarla ogni volta che questo mouse 3D è collegato.",
"ui.space_mouse_calibration_no_response": "Un asse si è mosso appena, quindi non si può calibrare. Ricomincia e muovi la manopola più a fondo ogni volta.",
"ui.space_mouse_dead_zone": "Zona morta",
"ui.space_mouse_dead_zone_tip": "Quanto deve muoversi la manopola prima che si muova la vista, come quota della sua corsa completa. Aumentala se la vista si sposta con la manopola a riposo.",
"ui.space_mouse_driven_by": "Quale movimento calibrato del dispositivo comanda questo asse. Se ne scegli uno già usato da un altro asse, i due si scambiano.",
"ui.space_mouse_driven_by_label": "comandato da",
"ui.space_mouse_driver": "Driver del mouse 3D",
"ui.space_mouse_driver_auto": "Automatico",
"ui.space_mouse_driver_direct": "Diretto (hidraw)",
"ui.space_mouse_driver_only_direct": "Lettura diretta del dispositivo; spacenavd non è in esecuzione",
"ui.space_mouse_driver_only_spacenavd": "Lettura tramite spacenavd, l'unica possibilità disponibile",
"ui.space_mouse_forget": "Dimentica calibrazione",
"ui.space_mouse_hint": "Sposta e ruota la vista insieme; gli schizzi usano solo panoramica e zoom.",
"ui.space_mouse_motion_rotation_x": "Rotazione X",
"ui.space_mouse_motion_rotation_y": "Rotazione Y",
"ui.space_mouse_motion_rotation_z": "Rotazione Z",
"ui.space_mouse_motion_translation_x": "Traslazione X",
"ui.space_mouse_motion_translation_y": "Traslazione Y",
"ui.space_mouse_motion_translation_z": "Traslazione Z",
"ui.space_mouse_navigate": "Naviga con SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Nessun mouse 3D trovato",
"ui.space_mouse_not_calibrated": "Non calibrato",
"ui.space_mouse_reading": "Lettura",
"ui.space_mouse_reset_axes": "Ripristina assegnazione assi",
"ui.space_mouse_step_lift": "Solleva la manopola, tienila, poi lasciala.",
"ui.space_mouse_step_press": "Premi la manopola verso il basso, tienila, poi lasciala.",
"ui.space_mouse_step_pull": "Tira la manopola verso di te, tienila, poi lasciala.",
"ui.space_mouse_step_push": "Spingi la manopola lontano da te, tienila, poi lasciala.",
"ui.space_mouse_step_rest": "Togli la mano dalla manopola e lasciala ferma.",
"ui.space_mouse_step_slide_left": "Fai scorrere la manopola a sinistra, tienila, poi lasciala.",
"ui.space_mouse_step_slide_right": "Fai scorrere la manopola a destra, tienila, poi lasciala.",
"ui.space_mouse_step_tilt_back": "Inclina la manopola all'indietro (bordo anteriore in alto), tienila, poi lasciala.",
"ui.space_mouse_step_tilt_forward": "Inclina la manopola in avanti (bordo anteriore in basso), tienila, poi lasciala.",
"ui.space_mouse_step_tilt_left": "Inclina la manopola a sinistra (bordo sinistro in basso), tienila, poi lasciala.",
"ui.space_mouse_step_tilt_right": "Inclina la manopola a destra (bordo destro in basso), tienila, poi lasciala.",
"ui.space_mouse_step_twist_clockwise": "Ruota la manopola in senso orario, tienila, poi lasciala.",
"ui.space_mouse_step_twist_counterclockwise": "Ruota la manopola in senso antiorario, tienila, poi lasciala.",
"ui.space_mouse_view_axis": "Asse della vista",
"ui.space_mouse_view_off": "Disattivato",
"ui.space_mouse_view_off_tip": "Il profilo del driver del mouse 3D disattiva questo asse, quindi il suo movimento non muove la vista. Se qui scegli il movimento di un altro asse, i due si scambiano e quel movimento smette di muovere la vista.",
"ui.space_mouse_view_orbit_sideways": "Orbita sinistra / destra",
"ui.space_mouse_view_orbit_up_down": "Orbita su / giù",
"ui.space_mouse_view_pan_sideways": "Panoramica sinistra / destra",
"ui.space_mouse_view_pan_up_down": "Panoramica su / giù",
"ui.space_mouse_view_roll": "Rollio",
"ui.space_mouse_view_unmapped": "Asse {n}",
"ui.space_mouse_view_zoom": "Zoom",
"ui.spacing": "Spaziatura",
"ui.spacing_2": "Spaziatura 2",
"ui.sphere": "Sfera",
+841 -56
View File
File diff suppressed because it is too large Load Diff
+245 -55
View File
@@ -72,6 +72,7 @@
"Choose…": "선택…",
"Circular": "원형",
"Circumference": "원주",
"Clockwise": "시계 방향",
"Close draft": "초안 닫기",
"Collisions": "충돌",
"Colour": "색상",
@@ -323,6 +324,7 @@
"Search commands": "명령 검색",
"Search this library": "이 라이브러리 검색",
"Section": "단면",
"Section position": "단면 위치",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "다른 항목 선택: 모델 위에서 왼쪽 버튼을 가만히 누르고 있거나(또는 오른쪽 클릭 › 다른 항목 선택…) 커서 아래의 모든 항목을 나열합니다. 같은 위치를 다시 클릭하면 다음 항목이 선택됩니다. 가운데 버튼을 더블 클릭하면 홈으로 이동합니다(환경설정 › 탐색).",
"Select bodies (a face picks its body)": "바디 선택(면을 클릭하면 해당 바디 선택)",
"Select bodies or faces instead": "대신 바디 또는 면을 선택하세요",
@@ -346,9 +348,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "이 디자인들이 저장되기 전에 SolidCraft가 종료되었습니다. 마지막 자동 저장본을 열 수 있습니다.",
"Specialise": "특수화",
"Speed": "속도",
"Square": "정사각형",
"Stand it up": "세우기",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "세우기: X축 기준 90도 회전으로, 실행 취소할 수 있는 하나의 이동 피처로 적용됩니다.",
"Stand this part up?": "이 부품을 세울까요?",
"Start angle": "시작 각도",
"Start bulge": "시작 벌지",
"Start continuity": "시작 연속성",
"Step": "단계",
@@ -413,6 +417,7 @@
"angle, height": "각도, 높이",
"appearance.dielectric_ior": "유전체 굴절률",
"appearance.metallic": "금속성",
"appearance.only_available": "사용 가능한 외관만",
"appearance.pbr_help": "이 표면은 렌더링 작업 공간이나 제품 PNG에서 확인하세요. 굴절과 투과는 지원하지 않습니다.",
"appearance.pbr_opaque": "불투명 PBR",
"appearance.roughness": "거칠기",
@@ -531,6 +536,10 @@
"cmd.file_insert_step": "STEP 삽입",
"cmd.file_save_mesh": "메쉬로 저장",
"cmd.file_slicers": "설치된 슬라이서",
"cmd.frame_member": "프레임 부재",
"cmd.frame_profile_insert": "프로파일 삽입",
"cmd.frame_profile_list": "프로파일 라이브러리",
"cmd.frame_profile_properties": "프로파일 속성",
"cmd.inspect_accessibility": "접근성 분석",
"cmd.inspect_center_of_mass": "무게중심",
"cmd.inspect_clearance": "여유 간격",
@@ -570,6 +579,7 @@
"cmd.material_create": "새 물리 재질",
"cmd.material_delete": "물리 재질 삭제",
"cmd.material_edit": "물리 재질 편집",
"cmd.material_inspect": "물리 재질 검사",
"cmd.material_list": "재질 목록",
"cmd.material_properties_directional": "방향별 탄성 계수",
"cmd.material_properties_validate": "물리 재질 데이터 검증",
@@ -616,13 +626,32 @@
"cmd.plastic_rules": "플라스틱 규칙 목록",
"cmd.plastic_snap_fit": "스냅핏",
"cmd.render_backends": "렌더링 백엔드",
"cmd.render_camera_fit": "렌더 대상 화면 맞춤",
"cmd.render_camera_get": "렌더 카메라 확인",
"cmd.render_cancel": "렌더링 취소",
"cmd.render_export": "PNG 내보내기",
"cmd.render_hdr_inspect": "HDR 소스 검사",
"cmd.render_library_appearance_apply": "라이브러리 불투명 외관 적용",
"cmd.render_library_describe": "렌더 리소스 검사",
"cmd.render_library_environment_apply": "내장 HDR 환경 적용",
"cmd.render_library_list": "렌더 라이브러리 탐색",
"cmd.render_library_physical_prepare": "Prepare Physical Reference",
"cmd.render_library_studio_apply": "제작된 스튜디오 조명 적용",
"cmd.render_library_studio_prepare": "제작된 스튜디오 조명 준비",
"cmd.render_library_surface_bind": "Save Surface Binding",
"cmd.render_library_surface_prepare": "제작된 표면 맵 준비",
"cmd.render_library_surface_unbind": "Remove Surface Binding",
"cmd.render_library_targets": "렌더 리소스 대상 찾기",
"cmd.render_preflight": "렌더 요청 확인",
"cmd.render_result_pin": "표시된 렌더 결과 유지",
"cmd.render_scene": "렌더링 장면 검사",
"cmd.render_settings_apply": "렌더링 카메라 적용",
"cmd.render_settings_capture_hdr": "HDR과 렌더 설정 저장",
"cmd.render_settings_get": "렌더링 설정",
"cmd.render_settings_set": "렌더링 설정 저장",
"cmd.render_stage_backdrop": "물리 렌더 배경 저장",
"cmd.render_stage_clear": "Remove Render Stage",
"cmd.render_stage_ground": "Save Render Ground",
"cmd.render_start": "이미지 렌더링",
"cmd.render_status": "렌더링 진행 상황",
"cmd.select_clear": "선택 지우기",
@@ -1000,13 +1029,33 @@
"recovery.just_now": "방금",
"recovery.min_ago": "{count}분 전",
"recovery.never_saved": "저장된 적 없음",
"render.add_ground": "Add fitted ground to setup",
"render.background": "장면 배경",
"render.bloom_enable": "표시 블룸",
"render.bloom_help": "양의 선형 하이라이트를 세 번의 정규화 필터로 확산하는 출력 전용 효과입니다. 반경은 출력 픽셀 단위이며 sRGB 전에 더합니다. 렌즈·볼륨·수렴 개선이 아니며 원본 EXR은 변하지 않습니다.",
"render.bloom_opaque": "블룸에는 불투명 출력이 필요합니다. 투명 PNG를 몰래 합성하지 않고 거부합니다.",
"render.bloom_radius": "블룸 반경",
"render.bloom_strength": "블룸 강도",
"render.bloom_threshold": "선형 하이라이트 임계값",
"render.clear_stage": "Remove saved stage",
"render.cpu_budget": "CPU 리소스 프로필",
"render.cpu_budget_bodies": "바디 수 상한",
"render.cpu_budget_compact": "컴팩트 (기존 제한)",
"render.cpu_budget_details": "리소스 제한",
"render.cpu_budget_help": "더 큰 제한은 명시적으로 선택합니다. 해상도와 샘플 수를 자동으로 줄이지 않습니다.",
"render.cpu_budget_memory": "작업 메모리 상한",
"render.cpu_budget_not_guarantee": "이 값은 허용 상한이며 RAM 예약이나 속도 보장이 아닙니다. 형상, 필름, 보관된 미리보기가 메모리 예산을 공유합니다. 렌더링은 취소할 수 있습니다.",
"render.cpu_budget_resize": "설정이 CPU 프로필이나 카메라 샘플 예산을 초과합니다. 더 작은 크기를 명시적으로 적용하거나 더 큰 프로필을 선택하세요.",
"render.cpu_budget_studio": "스튜디오 (큰 이미지)",
"render.cpu_budget_triangles": "삼각형 수 상한",
"render.cpu_budget_workstation": "워크스테이션 (대규모 장면)",
"render.exr_pending": "GPU는 원본 선형 EXR을 보존할 수 있습니다. 압축, 다중 레이어 이미지 및 보조 렌더 패스는 아직 사용할 수 없습니다.",
"render.fit": "제품에 맞추기",
"render.image_cancelled": "렌더링 취소됨",
"render.image_cancelling": "취소 중",
"render.image_completed": "이미지 내보내기 준비됨",
"render.image_encoding": "PNG 인코딩 중",
"render.image_export": "PNG 내보내기",
"render.image_export": "완료된 이미지 내보내기",
"render.image_failed": "렌더링 실패",
"render.image_height": "높이",
"render.image_help": "CPU 미리 보기 이미지 · PNG · 근사 투명도",
@@ -1024,17 +1073,98 @@
"render.image_unknown": "렌더 상태를 확인할 수 없습니다",
"render.image_use_largest": "이 비율에서 가장 큰 크기 사용:",
"render.image_width": "너비",
"render.include_rig_cards": "Include passive cards and flags",
"render.library_applied": "리소스 변환을 적용했습니다. 시작 전에 전체 렌더 요청을 확인하세요.",
"render.library_apply_hdr": "이 HDR 환경과 함께 저장",
"render.library_apply_maps": "제작된 GPU 표면 맵 사용",
"render.library_apply_studio": "제작된 스튜디오 변환 저장",
"render.library_apply_uniform": "균일한 불투명 외관 적용",
"render.library_authored_help": "독자적으로 제작한 리소스는 표현용이며 측정된 공학 데이터가 아닙니다. 각 동작은 명시된 변환만 적용합니다.",
"render.library_blocked": "이 리소스에는 미해결 보정 또는 외부 태양 광원이 필요하여 아직 적용할 수 없습니다.",
"render.library_center": "리그 중심",
"render.library_emitters_only_ack": "광원만 사용하고 명시된 수동 반사판은 생략",
"render.library_environment_concepts": "환경 구상",
"render.library_extent": "대상 크기",
"render.library_hdr": "내장 HDR 환경",
"render.library_hdr_help": "원본 HDR을 저장 설정에 포함합니다. 제작된 경도 원점을 명시적으로 변환하며 저장 후 외부 파일이 필요하지 않습니다.",
"render.library_light_concepts": "조명 구상",
"render.library_maps_help": "Built-in maps can be saved with pinned sources and local occurrence mapping. Temporary optical overrides remain a separate mode.",
"render.library_material_concepts": "재질 구상",
"render.library_next": "다음 페이지",
"render.library_physical_profiles": "Physical reference profiles",
"render.library_previous": "이전 페이지",
"render.library_quality_concepts": "품질 사양",
"render.library_replace_environment": "기존 환경을 명시적으로 교체",
"render.library_replace_studio": "Replace saved lighting, environment and stage",
"render.library_results": "일치하는 리소스",
"render.library_rig_help": "Fit once at an explicit center and physical extent. Complete rigs include passive cards; calibration blockers still refuse. Conservative emitter/card clearance is checked before saving.",
"render.library_rigs": "제작된 조명 리그",
"render.library_save_maps": "Save maps to this setup",
"render.library_scene_bounds": "캡처된 장면 범위 사용",
"render.library_scenes": "장면 사양",
"render.library_search": "라이브러리 검색",
"render.library_selene": "SELENE 불투명 외관",
"render.library_specification_only": "사양만 제공되며 완전히 실행 가능한 프리셋이 아닙니다. 필요한 효과, 리소스 및 대상 역할은 명시적인 구현과 연결이 필요합니다.",
"render.library_surfaces": "제작된 PBR 표면",
"render.library_target": "대상 바디",
"render.library_title": "렌더 리소스 라이브러리",
"render.library_uniform_ack": "공간 변화나 고급 효과 없는 균일 근사 사용",
"render.material_color_replace": "기본 맵으로 바디 색상 교체",
"render.material_limits": "최대 128개 바디 설정, 분리된 유리 32개, 코팅 64개, 텍스처 연결 128개. 같은 맵은 메모리를 공유하며 실제 장치 및 전체 메모리 제한은 유지됩니다.",
"render.material_metallic_map": "금속성 맵 (선형)",
"render.material_roughness_map": "거칠기 맵 (선형)",
"render.material_v_up": "텍스처 V축은 이미지 위쪽",
"render.navigation_help": "카메라는 디자인에서와 같이 움직입니다(환경설정 › 탐색). 설정 저장은 현재 카메라를 기록합니다.",
"render.opaque_image_summary": "미리보기 PNG · 불투명 PBR · 블렌딩 투명도",
"render.opaque_preview_help": "불투명 PBR 및 단색 표시, 블렌딩 투명도를 지원합니다. 굴절, HDRI 및 최종 렌더링은 아직 지원하지 않습니다.",
"render.output_format_status": "PNG는 완료된 표시 이미지 그대로입니다. 선택적 GPU EXR은 표시 필터 전 선형 FLOAT32 방사 휘도를 보존합니다. 미리보기 캐시나 다른 작업으로 바꾸어 내보내지 않습니다.",
"render.passive_stage": "Render-only stage",
"render.passive_stage_help": "Finite passive panels belong only to rendered images. They do not add CAD bodies or mass. Placement is fixed after applying; intersecting product bounds refuse. CPU does not provide diffuse bounce lighting.",
"render.perspective": "원근 카메라",
"render.physical_acknowledge": "I understand this partial reference needs grade, condition and source review",
"render.physical_associations": "Physical candidates and roles",
"render.physical_copy_plan": "Copy plan with sources and omissions",
"render.physical_material_name": "Proposed reference material name",
"render.physical_omissions": "Properties not representable in the native card",
"render.physical_plan_only": "Read-only plan prepared. No material was created or assigned. Keep its extended profile and omissions when reviewing the native card.",
"render.physical_prepare_plan": "Prepare creation plan (does not apply)",
"render.physical_separate": "Appearance is not material identification. These are sourced references, not certified effective properties. Existing mass, engineering materials and optics are unchanged.",
"render.physical_unknown": "No numerical candidate is established. Grade, formulation or construction must be specified; unknown is not zero.",
"render.preview": "통합 미리보기",
"render.preview_help": "래스터 셰이딩과 블렌딩 투명도를 사용합니다. 굴절, HDRI 및 최종 렌더링은 아직 지원하지 않습니다.",
"render.reload_settings": "저장된 설정 다시 불러오기",
"render.replace_saved_surface": "Replace an existing saved binding for this target",
"render.restore_camera": "저장된 카메라 복원",
"render.result_reduced_display": "표시 미리보기를 뷰포트 장치 또는 캐시 제한에 맞춰 축소했습니다. 보관된 PNG와 내보내기는 전체 해상도를 유지합니다. 이 축소 보기에서는 원본 100% 검사가 불가능합니다.",
"render.return_design": "설계로 돌아가기",
"render.ribbon_backgrounds": "배경",
"render.ribbon_check": "검사",
"render.ribbon_check_help": "렌더에 제출할 현재 설정을 그대로 검증합니다. 엔진 전환, 축소, 리소스 교체를 몰래 수행하지 않습니다.",
"render.ribbon_edit_lights": "조명 편집",
"render.ribbon_environment_help": "HDR 조명, 카메라 전용 배경, 물리 무대는 독립된 설정이며 교체에는 명시적 확인이 필요합니다.",
"render.ribbon_finish_dialog": "먼저 파일 또는 확인 대화 상자를 완료하세요.",
"render.ribbon_frame": "화면 맞춤",
"render.ribbon_hdr_library": "HDR 라이브러리",
"render.ribbon_job_active": "렌더링이 진행 중입니다. 새 작업 전에 명시적으로 취소하세요.",
"render.ribbon_light_rigs": "조명 구성",
"render.ribbon_lights_help": "조명 구성을 검토하거나 지원 조명을 편집합니다. 명시적으로 적용하기 전에는 현재 설정을 교체하지 않습니다.",
"render.ribbon_materials": "재질",
"render.ribbon_materials_help": "공용 외관 검사기와 원본 표면 라이브러리를 엽니다. 광학 편집은 공학 속성을 임의로 변경하지 않습니다.",
"render.ribbon_no_active_job": "취소할 활성 렌더링이 없습니다.",
"render.ribbon_no_result": "이미지를 내보내려면 렌더링을 완료하세요.",
"render.ribbon_no_saved_camera": "카메라 복원 전에 샷을 저장하세요.",
"render.ribbon_saved_camera": "저장 카메라",
"render.ribbon_shots": "샷",
"render.ribbon_studios": "스튜디오",
"render.ribbon_studios_help": "구현된 스튜디오를 찾아보고 적용 전에 교체 항목을 검토합니다. 개념 장면은 구분됩니다.",
"render.ribbon_surface_maps": "표면 맵",
"render.save_setup": "설정 저장",
"render.saved_maps_runtime_conflict": "Saved surface bindings cannot be mixed with temporary optical overrides or an unsaved HDR. Save the HDR or remove the temporary inputs explicitly.",
"render.saved_surface_remove": "Remove saved binding",
"render.saved_surfaces": "Saved surface bindings",
"render.saved_surfaces_help": "Pinned built-in maps follow the selected occurrence in local millimeters. A replaced or missing target is an error, never an automatic reassignment.",
"render.setup_name": "설정 이름",
"render.stage_panel_count": "Saved panels",
"render.unavailable_preview": "이 설정은 사용할 수 없는 미리보기 프로필을 요청합니다.",
"render.unsaved": "저장하지 않은 미리보기 변경 사항",
"render.use_preview": "통합 미리보기 사용",
@@ -1070,6 +1200,8 @@
"settings.msaa_applies_on_restart": "안티앨리어싱은 다음에 SolidCraft를 시작할 때 적용됩니다",
"settings.msaa_reset": "{setting}: {requested}×는 이 그래픽 장치에서 지원되지 않아 {reset}×로 변경했습니다(지원: {supported}).",
"settings.msaa_unsupported": "이 그래픽 장치에서 지원되지 않음",
"settings.reference_geometry_through_bodies": "바디를 통해 참조 형상 표시",
"settings.reference_geometry_through_bodies_help": "비활성 스케치 윤곽과 기준 평면을 음영 처리된 바디 위에 그립니다",
"settings.reset_intro": "다음 설정은 이 컴퓨터에서 작동하지 않아 SolidCraft를 시작할 수 없게 하므로 변경되었습니다:",
"settings.reset_title": "설정이 변경되었습니다",
"settings.supersampling_note": "뷰를 더 높은 해상도로 그린 뒤 축소합니다. 4K 미만 화면에서 모서리, 선, 그리드가 더 매끄러워집니다. GPU 시간이 듭니다: 1.5×에서 약 2.25배, 2×에서 4배의 픽셀.",
@@ -1120,12 +1252,115 @@
"toolbar.customize_note": "{tab} 탭입니다. 명령을 선택하면 도구 모음에 해당 버튼이 추가됩니다. 패널의 드롭다운에서 행을 도구 모음으로 끌거나, 버튼을 도구 모음 밖으로 끌어 제거하거나, 드롭다운 행의 고정 핀을 사용할 수도 있습니다.",
"toolbar.image": "이미지",
"toolbar.render": "렌더링",
"toolbar.render_appearance": "외관",
"toolbar.render_camera": "카메라",
"toolbar.render_environment": "환경",
"toolbar.render_lighting": "조명",
"toolbar.render_scene": "장면",
"trace.points": "경로를 따라 {count}개 점",
"trace.stopped_by_contact": "샘플 {sample}에서 끝남: {a}이(가) {b}을(를) 통과합니다",
"trace.stopped_by_loop": "샘플 {sample}에서 끝남: {joint}을(를) 지나는 루프를 더 이상 닫을 수 없습니다",
"ui.1_degree_of_freedom": "자유도 1개",
"ui.3d_mouse_axes_reassigned": "축이 환경설정에서 다시 지정되어 있으며, 이 보정 후에도 적용됩니다.",
"ui.3d_mouse_button": "버튼 {number}",
"ui.3d_mouse_button_nothing": "없음",
"ui.3d_mouse_button_settings": "3D 마우스 설정",
"ui.3d_mouse_buttons": "버튼",
"ui.3d_mouse_buttons_hint": "3D 마우스의 버튼을 누르면 여기에 나타납니다. 동작을 고르세요.",
"ui.3d_mouse_buttons_no_device": "버튼 동작을 정하려면 3D 마우스를 연결하세요.",
"ui.3d_mouse_buttons_reset": "기본 버튼으로 되돌리기",
"ui.3d_mouse_calibrate": "보정…",
"ui.3d_mouse_calibrate_tip": "창의 안내대로 캡을 움직이면 SolidCraft가 이 3D 마우스의 각 축이 어느 방향으로 얼마나 움직이는지 학습합니다. 이 3D 마우스에 저장됩니다.",
"ui.3d_mouse_calibrate_title": "3D 마우스 보정",
"ui.3d_mouse_calibrate_wait": "SolidCraft가 인식할 수 있도록 3D 마우스를 한 번 움직여 주세요.",
"ui.3d_mouse_calibrated": "보정됨",
"ui.3d_mouse_calibration": "3D 마우스 보정",
"ui.3d_mouse_calibration_done": "완료했습니다. 저장하면 이 3D 마우스를 연결할 때마다 사용됩니다.",
"ui.3d_mouse_calibration_no_response": "거의 움직이지 않은 축이 있어 보정할 수 없습니다. 처음부터 다시 하고 매번 캡을 더 멀리 움직이세요.",
"ui.3d_mouse_circles_help": "원은 보정 후 장치가 보내는 값을 보여 줍니다. 테스트 모델은 반전, 속도, 축 할당을 거쳐 화면이 그 값으로 하는 일을 보여 줍니다.",
"ui.3d_mouse_dead_zone": "데드존",
"ui.3d_mouse_dead_zone_tip": "뷰가 움직이기 전에 캡이 움직여야 하는 정도로, 전체 이동 범위에 대한 비율입니다. 캡을 건드리지 않았는데 뷰가 움직이면 높이세요.",
"ui.3d_mouse_denied": "SolidCraft가 3D 마우스를 찾았지만 읽을 권한이 없습니다. udev 규칙은 3D 마우스 설정 안내를 참고하세요.",
"ui.3d_mouse_denied_short": "찾았지만 읽을 수 없음",
"ui.3d_mouse_driven_by": "이 축을 움직이는 장치의 보정된 동작입니다. 다른 축이 이미 쓰는 동작을 고르면 둘이 서로 바뀝니다.",
"ui.3d_mouse_driven_by_label": "제어 동작",
"ui.3d_mouse_driver": "3D 마우스 드라이버",
"ui.3d_mouse_driver_auto": "자동",
"ui.3d_mouse_driver_direct": "직접 (hidraw)",
"ui.3d_mouse_driver_fallback": "지금은 spacenavd를 통해 읽는 중입니다. 장치 자체를 열 수 없었습니다.",
"ui.3d_mouse_driver_only_direct": "장치를 직접 읽는 중 (spacenavd가 실행 중이 아님)",
"ui.3d_mouse_driver_only_spacenavd": "spacenavd로 읽는 중 (다른 방법이 없음)",
"ui.3d_mouse_forget": "보정 삭제",
"ui.3d_mouse_ghost_left_viewport": "뷰포트 왼쪽",
"ui.3d_mouse_ghost_left_window": "창 왼쪽",
"ui.3d_mouse_ghost_position": "위치",
"ui.3d_mouse_ghost_right_viewport": "뷰포트 오른쪽",
"ui.3d_mouse_hint": "뷰를 함께 이동하고 회전합니다. 스케치는 이동과 확대/축소만 사용합니다.",
"ui.3d_mouse_kind_cap": "중앙 복귀",
"ui.3d_mouse_kind_question": "어떤 종류의 3D 마우스인가요?",
"ui.3d_mouse_kind_trackball": "트랙볼",
"ui.3d_mouse_looks_like_cap": "잡고 있는 동안 값이 그대로 유지됩니다. ‘중앙 복귀’ 방식 장치처럼요.",
"ui.3d_mouse_looks_like_trackball": "움직임을 멈추면 바로 값이 돌아옵니다. 트랙볼처럼요.",
"ui.3d_mouse_motion_rotation_x": "회전 X",
"ui.3d_mouse_motion_rotation_y": "회전 Y",
"ui.3d_mouse_motion_rotation_z": "회전 Z",
"ui.3d_mouse_motion_translation_x": "이동 X",
"ui.3d_mouse_motion_translation_y": "이동 Y",
"ui.3d_mouse_motion_translation_z": "이동 Z",
"ui.3d_mouse_navigate": "3D 마우스로 탐색",
"ui.3d_mouse_none": "3D 마우스를 찾을 수 없음",
"ui.3d_mouse_not_calibrated": "보정 안 됨",
"ui.3d_mouse_offer": "3D 마우스를 보정하면 바뀌거나 반전된 축이 바로잡히고 속도와 조작감이 맞춰집니다.",
"ui.3d_mouse_offer_never": "다시 표시하지 않음",
"ui.3d_mouse_offer_swap": "Hidraw(직접 드라이버)로 전환",
"ui.3d_mouse_reading": "읽은 값",
"ui.3d_mouse_reset_axes": "축 지정 초기화",
"ui.3d_mouse_reset_view": "뷰 초기화",
"ui.3d_mouse_restore": "3D 마우스 기본값 복원",
"ui.3d_mouse_restore_ask": "3D 마우스 설정을 기본값으로 되돌릴까요? 보정과 버튼 할당은 그대로 유지됩니다.",
"ui.3d_mouse_section": "3D 마우스",
"ui.3d_mouse_show_in_view": "3D 뷰에 3D 마우스 표시",
"ui.3d_mouse_show_in_view_tip": "왼쪽 아래에 3D 마우스 그림을 띄워 움직임을 따라 보여 줍니다.",
"ui.3d_mouse_skip": "내 장치는 이 동작을 못 해요",
"ui.3d_mouse_skip_tip": "이 동작을 양방향 모두 건너뛰고 보정에서 뺍니다.",
"ui.3d_mouse_speed": "3D 마우스 속도",
"ui.3d_mouse_step_lift": "캡을 위로 들어 유지한 다음 놓으세요.",
"ui.3d_mouse_step_press": "캡을 아래로 눌러 유지한 다음 놓으세요.",
"ui.3d_mouse_step_pull": "캡을 몸 쪽으로 당겨 유지한 다음 놓으세요.",
"ui.3d_mouse_step_push": "캡을 앞쪽으로 밀어 유지한 다음 놓으세요.",
"ui.3d_mouse_step_rest": "캡에서 손을 떼고 가만히 두세요.",
"ui.3d_mouse_step_rest_trackball": "볼에서 손을 떼고 가만히 두세요.",
"ui.3d_mouse_step_ring_clockwise": "스크롤 링을 시계 방향으로 몇 칸 돌리세요.",
"ui.3d_mouse_step_ring_counterclockwise": "스크롤 링을 시계 반대 방향으로 몇 칸 돌리세요.",
"ui.3d_mouse_step_roll_away": "볼을 바깥쪽으로 몇 번 굴린 다음 멈추게 두세요.",
"ui.3d_mouse_step_roll_left": "볼을 왼쪽으로 몇 번 굴린 다음 멈추게 두세요.",
"ui.3d_mouse_step_roll_right": "볼을 오른쪽으로 몇 번 굴린 다음 멈추게 두세요.",
"ui.3d_mouse_step_roll_toward": "볼을 몸 쪽으로 몇 번 굴린 다음 멈추게 두세요.",
"ui.3d_mouse_step_slide_left": "캡을 왼쪽으로 밀어 유지한 다음 놓으세요.",
"ui.3d_mouse_step_slide_right": "캡을 오른쪽으로 밀어 유지한 다음 놓으세요.",
"ui.3d_mouse_step_spin_clockwise": "볼을 시계 방향으로 몇 번 돌린 다음 멈추게 두세요.",
"ui.3d_mouse_step_spin_counterclockwise": "볼을 시계 반대 방향으로 몇 번 돌린 다음 멈추게 두세요.",
"ui.3d_mouse_step_tilt_away": "캡 윗부분을 바깥쪽으로 기울여(가까운 쪽 가장자리가 올라감) 유지한 다음 놓으세요.",
"ui.3d_mouse_step_tilt_left": "캡을 왼쪽으로 기울여(왼쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
"ui.3d_mouse_step_tilt_right": "캡을 오른쪽으로 기울여(오른쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
"ui.3d_mouse_step_tilt_toward": "캡 윗부분을 몸 쪽으로 기울여(가까운 쪽 가장자리가 내려감) 유지한 다음 놓으세요.",
"ui.3d_mouse_step_touch": "캡을 누르지 말고 손만 올려 두세요.",
"ui.3d_mouse_step_twist_clockwise": "캡을 시계 방향으로 돌려 유지한 다음 놓으세요.",
"ui.3d_mouse_step_twist_counterclockwise": "캡을 시계 반대 방향으로 돌려 유지한 다음 놓으세요.",
"ui.3d_mouse_switch_cap": "중앙 복귀형으로 보정",
"ui.3d_mouse_switch_trackball": "트랙볼로 보정",
"ui.3d_mouse_view_axis": "뷰 축",
"ui.3d_mouse_view_off": "끔",
"ui.3d_mouse_view_off_tip": "3D 마우스 드라이버 프로필이 이 축을 끄므로, 이 축의 동작으로는 뷰가 움직이지 않습니다. 여기서 다른 축의 동작을 고르면 둘이 바뀌고, 그 동작은 더 이상 뷰를 움직이지 않습니다.",
"ui.3d_mouse_view_orbit_sideways": "좌우 궤도",
"ui.3d_mouse_view_orbit_up_down": "상하 궤도",
"ui.3d_mouse_view_pan_sideways": "좌우 이동",
"ui.3d_mouse_view_pan_up_down": "상하 이동",
"ui.3d_mouse_view_roll": "롤",
"ui.3d_mouse_view_unmapped": "축 {n}",
"ui.3d_mouse_view_zoom": "확대/축소",
"ui.3d_mouse_waiting": "움직임 대기 중",
"ui.3d_mouse_wiggle": "연결되어 있다면 살짝 움직여 보세요.",
"ui.3d_sketch": "3D 스케치",
"ui.activate": "활성화",
"ui.active_component": "활성 컴포넌트",
@@ -1355,6 +1590,7 @@
"ui.blocked": "막힘",
"ui.bodies": "바디",
"ui.bodies_all_if_none": "바디 (없으면 전체)",
"ui.bottom": "저면",
"ui.box": "박스",
"ui.break": "끊기",
"ui.browser_bodies_one": "{count}개 바디",
@@ -1507,6 +1743,7 @@
"ui.grid_follows_view_hint": "측면 뷰에서는 지면 대신 마주 보는 수직 평면에 격자를 표시합니다",
"ui.ground_shadow": "바닥 그림자",
"ui.groupSelected": "선택 항목 그룹화",
"ui.hatch": "해칭",
"ui.heading_add_ins": "애드인",
"ui.heading_assemble": "조립",
"ui.heading_box": "박스",
@@ -1768,6 +2005,12 @@
"ui.sent_to_slicer": "{slicer}(으)로 보냈습니다",
"ui.sheet_gap": "틈",
"ui.shell": "쉘",
"ui.shortcut_capture_add_key": "일반 키 하나를 추가하세요. Esc를 누르면 취소됩니다.",
"ui.shortcut_capture_hint": "단축키를 클릭한 다음 보조 키와 일반 키 하나를 누르세요. 1초 이내에 보조 키를 추가하고 모든 키를 놓아 확정하세요. Esc를 누르면 취소됩니다.",
"ui.shortcut_capture_press": "보조 키와 일반 키 하나를 누르세요. Esc를 누르면 취소됩니다.",
"ui.shortcut_capture_release": "모든 키를 놓아 확정하세요. Esc를 누르면 취소됩니다.",
"ui.shortcut_capture_settling": "단축키 확인 중… Esc를 누르면 취소됩니다.",
"ui.shortcut_multiple_keys": "보조 키와 일반 키 하나만 사용하세요. 단축키를 클릭하여 다시 시도하세요.",
"ui.show": "표시",
"ui.showAll": "모두 표시",
"ui.showAllBodies": "모두 표시",
@@ -1797,59 +2040,6 @@
"ui.smooth_wheel_zoom": "부드러운 휠 확대/축소",
"ui.smooth_wheel_zoom_tip": "끄면 휠 이벤트를 부드러운 꼬리 없이 즉시 적용합니다",
"ui.snap": "스냅",
"ui.space_mouse_axes_reassigned": "축이 환경설정에서 다시 지정되어 있으며, 이 보정 후에도 적용됩니다.",
"ui.space_mouse_calibrate": "보정…",
"ui.space_mouse_calibrate_tip": "창의 안내대로 캡을 움직이면 SolidCraft가 이 3D 마우스의 각 축이 어느 방향으로 얼마나 움직이는지 학습합니다. 이 3D 마우스에 저장됩니다.",
"ui.space_mouse_calibrate_title": "3D 마우스 보정",
"ui.space_mouse_calibrated": "보정됨",
"ui.space_mouse_calibration": "3D 마우스 보정",
"ui.space_mouse_calibration_done": "완료했습니다. 저장하면 이 3D 마우스를 연결할 때마다 사용됩니다.",
"ui.space_mouse_calibration_no_response": "거의 움직이지 않은 축이 있어 보정할 수 없습니다. 처음부터 다시 하고 매번 캡을 더 멀리 움직이세요.",
"ui.space_mouse_dead_zone": "데드존",
"ui.space_mouse_dead_zone_tip": "보기가 움직이기 전에 캡이 움직여야 하는 정도로, 전체 이동 범위에 대한 비율입니다. 캡을 건드리지 않았는데 보기가 움직이면 높이세요.",
"ui.space_mouse_driven_by": "이 축을 움직이는 장치의 보정된 동작입니다. 다른 축이 이미 쓰는 동작을 고르면 둘이 서로 바뀝니다.",
"ui.space_mouse_driven_by_label": "제어 동작",
"ui.space_mouse_driver": "3D 마우스 드라이버",
"ui.space_mouse_driver_auto": "자동",
"ui.space_mouse_driver_direct": "직접 (hidraw)",
"ui.space_mouse_driver_only_direct": "장치를 직접 읽는 중 (spacenavd가 실행 중이 아님)",
"ui.space_mouse_driver_only_spacenavd": "spacenavd로 읽는 중 (다른 방법이 없음)",
"ui.space_mouse_forget": "보정 삭제",
"ui.space_mouse_hint": "뷰를 함께 이동하고 회전합니다. 스케치는 이동과 확대/축소만 사용합니다.",
"ui.space_mouse_motion_rotation_x": "회전 X",
"ui.space_mouse_motion_rotation_y": "회전 Y",
"ui.space_mouse_motion_rotation_z": "회전 Z",
"ui.space_mouse_motion_translation_x": "이동 X",
"ui.space_mouse_motion_translation_y": "이동 Y",
"ui.space_mouse_motion_translation_z": "이동 Z",
"ui.space_mouse_navigate": "SpaceNavigator / SpaceMouse로 탐색",
"ui.space_mouse_none": "3D 마우스를 찾을 수 없음",
"ui.space_mouse_not_calibrated": "보정 안 됨",
"ui.space_mouse_reading": "읽은 값",
"ui.space_mouse_reset_axes": "축 지정 초기화",
"ui.space_mouse_step_lift": "캡을 위로 들어 유지한 다음 놓으세요.",
"ui.space_mouse_step_press": "캡을 아래로 눌러 유지한 다음 놓으세요.",
"ui.space_mouse_step_pull": "캡을 몸 쪽으로 당겨 유지한 다음 놓으세요.",
"ui.space_mouse_step_push": "캡을 앞쪽으로 밀어 유지한 다음 놓으세요.",
"ui.space_mouse_step_rest": "캡에서 손을 떼고 가만히 두세요.",
"ui.space_mouse_step_slide_left": "캡을 왼쪽으로 밀어 유지한 다음 놓으세요.",
"ui.space_mouse_step_slide_right": "캡을 오른쪽으로 밀어 유지한 다음 놓으세요.",
"ui.space_mouse_step_tilt_back": "캡을 뒤로 기울여(앞쪽 가장자리를 위로) 유지한 다음 놓으세요.",
"ui.space_mouse_step_tilt_forward": "캡을 앞으로 기울여(앞쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
"ui.space_mouse_step_tilt_left": "캡을 왼쪽으로 기울여(왼쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
"ui.space_mouse_step_tilt_right": "캡을 오른쪽으로 기울여(오른쪽 가장자리를 아래로) 유지한 다음 놓으세요.",
"ui.space_mouse_step_twist_clockwise": "캡을 시계 방향으로 돌려 유지한 다음 놓으세요.",
"ui.space_mouse_step_twist_counterclockwise": "캡을 시계 반대 방향으로 돌려 유지한 다음 놓으세요.",
"ui.space_mouse_view_axis": "보기 축",
"ui.space_mouse_view_off": "끔",
"ui.space_mouse_view_off_tip": "3D 마우스 드라이버 프로필이 이 축을 끄므로, 이 축의 동작으로는 뷰가 움직이지 않습니다. 여기서 다른 축의 동작을 고르면 둘이 바뀌고, 그 동작은 더 이상 뷰를 움직이지 않습니다.",
"ui.space_mouse_view_orbit_sideways": "좌우 궤도",
"ui.space_mouse_view_orbit_up_down": "상하 궤도",
"ui.space_mouse_view_pan_sideways": "좌우 이동",
"ui.space_mouse_view_pan_up_down": "상하 이동",
"ui.space_mouse_view_roll": "롤",
"ui.space_mouse_view_unmapped": "축 {n}",
"ui.space_mouse_view_zoom": "확대/축소",
"ui.spacing": "간격",
"ui.spacing_2": "간격 2",
"ui.sphere": "구",
@@ -1891,7 +2081,7 @@
"ui.top": "상면",
"ui.torus": "토러스",
"ui.total_angle": "전진 각도",
"ui.trackpad_pan_tip": "트랙패드 제스처로 이동(끔: 휠처럼 확대/축소). 마우스 휠은 확대/축소, 엄지 휠은 좌우 이동, 핀치는 항상 확대/축소합니다",
"ui.trackpad_pan_tip": "두 손가락 스크롤로 화면을 이동합니다. Fusion에서는 Shift + 두 손가락 스크롤로 회전합니다. 끄면 스크롤로 확대/축소합니다. 마우스 휠과 핀치는 확대/축소하고, 엄지 휠은 좌우로 이동합니다.",
"ui.trim": "트림",
"ui.twist": "비틀림",
"ui.two_finger_scroll_pans": "두 손가락 스크롤로 이동",
+111 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Pilih…",
"Circular": "Bulat",
"Circumference": "Lilitan",
"Clockwise": "Ikut arah jam",
"Close draft": "Tutup draf",
"Collisions": "Perlanggaran",
"Colour": "Warna",
@@ -322,6 +323,7 @@
"Search commands": "Cari arahan",
"Search this library": "Cari dalam koleksi ini",
"Section": "Keratan",
"Section position": "Kedudukan keratan",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Pilih Lain: tahan butang kiri pada model tanpa bergerak (atau klik kanan › Pilih Lain…) untuk senarai objek di bawah kursor. Klik tempat sama untuk objek seterusnya. Klik dua kali butang tengah untuk ke Utama (Tetapan › Navigasi).",
"Select bodies (a face picks its body)": "Pilih jasad (klik muka untuk pilih jasadnya)",
"Select bodies or faces instead": "Pilih jasad atau muka",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft ditutup sebelum reka bentuk disimpan. Buka simpanan automatik terakhirnya.",
"Specialise": "Khususkan",
"Speed": "Kelajuan",
"Square": "Segi empat sama",
"Stand it up": "Tegakkan",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Tegakkan: suku putaran pada X, satu ciri Alih yang boleh dibatalkan.",
"Stand this part up?": "Tegakkan bahagian ini?",
"Start angle": "Sudut mula",
"Start bulge": "Bonjolan permulaan",
"Start continuity": "Keselanjaran permulaan",
"Step": "Langkah",
@@ -412,6 +416,7 @@
"angle, height": "sudut, ketinggian",
"appearance.dielectric_ior": "IOR Dielektrik",
"appearance.metallic": "Sifat Logam",
"appearance.only_available": "Rupa yang tersedia sahaja",
"appearance.pbr_help": "Lihat dalam Render atau PNG produk. Tiada pembiasan atau penembusan cahaya.",
"appearance.pbr_opaque": "PBR Legap",
"appearance.roughness": "Kekasaran",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Sisip STEP",
"cmd.file_save_mesh": "Simpan sebagai Mesh",
"cmd.file_slicers": "Penghiris Terpasang",
"cmd.frame_member": "Anggota Rangka",
"cmd.frame_profile_insert": "Sisipkan Profil",
"cmd.frame_profile_list": "Pustaka Profil",
"cmd.frame_profile_properties": "Sifat Profil",
"cmd.inspect_accessibility": "Analisis Kebolehcapaian",
"cmd.inspect_center_of_mass": "Pusat Jisim",
"cmd.inspect_clearance": "Kelegaan",
@@ -569,6 +578,7 @@
"cmd.material_create": "Bahan Fizikal Baharu",
"cmd.material_delete": "Padam Bahan Fizikal",
"cmd.material_edit": "Sunting Bahan Fizikal",
"cmd.material_inspect": "Periksa Bahan Fizikal",
"cmd.material_list": "Senarai Bahan",
"cmd.material_properties_directional": "Modulus Keanjalan Berarah",
"cmd.material_properties_validate": "Sahkan Data Bahan Fizikal",
@@ -1121,7 +1131,106 @@
"trace.stopped_by_contact": "Berakhir pada sampel {sample}: {a} akan menembusi {b}",
"trace.stopped_by_loop": "Berakhir pada sampel {sample}: gelung melalui {joint} tidak lagi dapat ditutup",
"ui.1_degree_of_freedom": "1 darjah kebebasan",
"ui.3d_mouse_axes_reassigned": "Paksi ditetapkan semula dalam Keutamaan, dan itu masih terpakai di atas penentukuran ini.",
"ui.3d_mouse_button": "Butang {number}",
"ui.3d_mouse_button_nothing": "Tiada",
"ui.3d_mouse_button_settings": "Tetapan tetikus 3D",
"ui.3d_mouse_buttons": "Butang",
"ui.3d_mouse_buttons_hint": "Tekan butang pada tetikus 3D anda untuk mencarinya di sini, kemudian pilih fungsinya.",
"ui.3d_mouse_buttons_no_device": "Sambungkan tetikus 3D untuk menetapkan fungsi butangnya.",
"ui.3d_mouse_buttons_reset": "Pulihkan butang lalai",
"ui.3d_mouse_calibrate": "Tentukur…",
"ui.3d_mouse_calibrate_tip": "Gerakkan penutup seperti yang diminta tetingkap, supaya SolidCraft mempelajari arah dan jarak setiap paksi tetikus 3D ini. Disimpan untuk tetikus 3D ini.",
"ui.3d_mouse_calibrate_title": "Tentukur tetikus 3D",
"ui.3d_mouse_calibrate_wait": "Gerakkan tetikus 3D sekali supaya SolidCraft dapat melihatnya.",
"ui.3d_mouse_calibrated": "Telah ditentukur",
"ui.3d_mouse_calibration": "Penentukuran tetikus 3D",
"ui.3d_mouse_calibration_done": "Selesai. Simpan untuk digunakan setiap kali tetikus 3D ini disambungkan.",
"ui.3d_mouse_calibration_no_response": "Satu paksi hampir tidak bergerak, jadi tidak dapat ditentukur. Mulakan semula dan gerakkan penutup lebih jauh setiap kali.",
"ui.3d_mouse_circles_help": "Bulatan menunjukkan apa yang dihantar peranti selepas penentukuran. Model ujian menunjukkan apa yang dilakukan paparan dengannya, selepas songsangan, kelajuan dan penetapan paksi.",
"ui.3d_mouse_dead_zone": "Zon mati",
"ui.3d_mouse_dead_zone_tip": "Sejauh mana penutup perlu bergerak sebelum pandangan bergerak, sebagai bahagian daripada julat penuhnya. Naikkan jika pandangan hanyut semasa penutup dalam keadaan rehat.",
"ui.3d_mouse_denied": "SolidCraft nampak tetikus 3D tetapi tidak dibenarkan membacanya. Lihat nota persediaan tetikus 3D untuk peraturan udev.",
"ui.3d_mouse_denied_short": "Dijumpai, tetapi tidak boleh dibaca",
"ui.3d_mouse_driven_by": "Gerakan peranti yang telah ditentukur yang memacu paksi ini. Memilih gerakan yang sudah digunakan paksi lain akan menukar kedua-duanya.",
"ui.3d_mouse_driven_by_label": "dipacu oleh",
"ui.3d_mouse_driver": "Pemacu tetikus 3D",
"ui.3d_mouse_driver_auto": "Automatik",
"ui.3d_mouse_driver_direct": "Terus (hidraw)",
"ui.3d_mouse_driver_fallback": "Buat masa ini dibaca melalui spacenavd: peranti itu sendiri tidak dapat dibuka.",
"ui.3d_mouse_driver_only_direct": "Membaca peranti secara terus; spacenavd tidak berjalan",
"ui.3d_mouse_driver_only_spacenavd": "Membaca melalui spacenavd, satu-satunya cara yang ada",
"ui.3d_mouse_forget": "Lupakan penentukuran",
"ui.3d_mouse_ghost_left_viewport": "Viewport kiri",
"ui.3d_mouse_ghost_left_window": "Tetingkap kiri",
"ui.3d_mouse_ghost_position": "Kedudukan",
"ui.3d_mouse_ghost_right_viewport": "Viewport kanan",
"ui.3d_mouse_hint": "Alih dan putar pandangan serentak; dalam lakaran, hanya sorot dan zum digunakan.",
"ui.3d_mouse_kind_cap": "Kembali ke tengah",
"ui.3d_mouse_kind_question": "Apakah jenis tetikus 3D ini?",
"ui.3d_mouse_kind_trackball": "Bola jejak",
"ui.3d_mouse_looks_like_cap": "Bacaan kekal selagi anda menahannya, seperti peranti jenis “Kembali ke tengah”.",
"ui.3d_mouse_looks_like_trackball": "Bacaan kembali sebaik sahaja anda berhenti bergerak, seperti bola jejak.",
"ui.3d_mouse_motion_rotation_x": "Putaran X",
"ui.3d_mouse_motion_rotation_y": "Putaran Y",
"ui.3d_mouse_motion_rotation_z": "Putaran Z",
"ui.3d_mouse_motion_translation_x": "Anjakan X",
"ui.3d_mouse_motion_translation_y": "Anjakan Y",
"ui.3d_mouse_motion_translation_z": "Anjakan Z",
"ui.3d_mouse_navigate": "Navigasi dengan tetikus 3D",
"ui.3d_mouse_none": "Tiada tetikus 3D ditemui",
"ui.3d_mouse_not_calibrated": "Belum ditentukur",
"ui.3d_mouse_offer": "Menentukur tetikus 3D membetulkan paksi yang bertukar atau terbalik dan menetapkan kelajuan serta rasanya.",
"ui.3d_mouse_offer_never": "Jangan tunjuk lagi",
"ui.3d_mouse_offer_swap": "Tukar ke Hidraw (pemacu terus)",
"ui.3d_mouse_reading": "Bacaan",
"ui.3d_mouse_reset_axes": "Tetapkan semula penetapan paksi",
"ui.3d_mouse_reset_view": "Set semula paparan",
"ui.3d_mouse_restore": "Pulihkan lalai tetikus 3D",
"ui.3d_mouse_restore_ask": "Tetapkan semula tetapan tetikus 3D kepada lalai? Penentukuran dan pemetaan butang kekal.",
"ui.3d_mouse_section": "Tetikus 3D",
"ui.3d_mouse_show_in_view": "Tunjukkan tetikus 3D dalam paparan 3D",
"ui.3d_mouse_show_in_view_tip": "Gambar tetikus 3D anda di sudut kiri bawah, yang bergerak apabila anda menggerakkannya.",
"ui.3d_mouse_skip": "Peranti saya tidak boleh buat ini",
"ui.3d_mouse_skip_tip": "Langkau gerakan ini kedua-dua arah; penentukuran tidak menggunakannya.",
"ui.3d_mouse_speed": "Kelajuan tetikus 3D",
"ui.3d_mouse_step_lift": "Angkat penutup ke atas, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_press": "Tekan penutup ke bawah, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_pull": "Tarik penutup ke arah anda, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_push": "Tolak penutup menjauhi anda, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_rest": "Alihkan tangan anda daripada penutup dan biarkan ia diam.",
"ui.3d_mouse_step_rest_trackball": "Alihkan tangan anda daripada bola dan biarkan ia diam.",
"ui.3d_mouse_step_ring_clockwise": "Putar gelang tatal ikut arah jam beberapa klik.",
"ui.3d_mouse_step_ring_counterclockwise": "Putar gelang tatal lawan arah jam beberapa klik.",
"ui.3d_mouse_step_roll_away": "Gulingkan bola menjauhi anda beberapa kali, kemudian biarkan ia berhenti.",
"ui.3d_mouse_step_roll_left": "Gulingkan bola ke kiri beberapa kali, kemudian biarkan ia berhenti.",
"ui.3d_mouse_step_roll_right": "Gulingkan bola ke kanan beberapa kali, kemudian biarkan ia berhenti.",
"ui.3d_mouse_step_roll_toward": "Gulingkan bola ke arah anda beberapa kali, kemudian biarkan ia berhenti.",
"ui.3d_mouse_step_slide_left": "Luncurkan penutup ke kiri, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_slide_right": "Luncurkan penutup ke kanan, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_spin_clockwise": "Pusing bola ikut arah jam beberapa kali, kemudian biarkan ia berhenti.",
"ui.3d_mouse_step_spin_counterclockwise": "Pusing bola lawan arah jam beberapa kali, kemudian biarkan ia berhenti.",
"ui.3d_mouse_step_tilt_away": "Condongkan bahagian atas penutup menjauhi anda (tepi terdekat naik), tahan, kemudian lepaskan.",
"ui.3d_mouse_step_tilt_left": "Condongkan penutup ke kiri (tepi kiri turun), tahan, kemudian lepaskan.",
"ui.3d_mouse_step_tilt_right": "Condongkan penutup ke kanan (tepi kanan turun), tahan, kemudian lepaskan.",
"ui.3d_mouse_step_tilt_toward": "Condongkan bahagian atas penutup ke arah anda (tepi terdekat turun), tahan, kemudian lepaskan.",
"ui.3d_mouse_step_touch": "Letakkan tangan anda pada penutup tanpa menolaknya.",
"ui.3d_mouse_step_twist_clockwise": "Pusing penutup ikut arah jam, tahan, kemudian lepaskan.",
"ui.3d_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
"ui.3d_mouse_switch_cap": "Tentukur sebagai kembali ke tengah",
"ui.3d_mouse_switch_trackball": "Tentukur sebagai bola jejak",
"ui.3d_mouse_view_axis": "Paksi paparan",
"ui.3d_mouse_view_off": "Mati",
"ui.3d_mouse_view_off_tip": "Profil pemacu tetikus 3D mematikan paksi ini, jadi gerakannya tidak menggerakkan paparan. Memilih gerakan paksi lain di sini menukar kedua-duanya, dan gerakan itu tidak lagi menggerakkan paparan.",
"ui.3d_mouse_view_orbit_sideways": "Orbit kiri / kanan",
"ui.3d_mouse_view_orbit_up_down": "Orbit atas / bawah",
"ui.3d_mouse_view_pan_sideways": "Pan kiri / kanan",
"ui.3d_mouse_view_pan_up_down": "Pan atas / bawah",
"ui.3d_mouse_view_roll": "Gelek",
"ui.3d_mouse_view_unmapped": "Paksi {n}",
"ui.3d_mouse_view_zoom": "Zum",
"ui.3d_mouse_waiting": "Menunggu pergerakan",
"ui.3d_mouse_wiggle": "Jika anda ada yang disambungkan, gerakkannya sedikit.",
"ui.3d_sketch": "Lakaran 3D",
"ui.activate": "Aktifkan",
"ui.active_component": "Komponen aktif",
@@ -1351,6 +1460,7 @@
"ui.blocked": "Disekat",
"ui.bodies": "Jasad",
"ui.bodies_all_if_none": "Jasad (semua jika tiada)",
"ui.bottom": "Bawah",
"ui.box": "Kotak",
"ui.break": "Putuskan",
"ui.browser_bodies_one": "{count} jasad",
@@ -1503,6 +1613,7 @@
"ui.grid_follows_view_hint": "Pandangan sisi guna grid satah tegak yang menghadapnya, bukan satah tanah",
"ui.ground_shadow": "Bayang tanah",
"ui.groupSelected": "Kumpulkan Pilihan",
"ui.hatch": "Lorek",
"ui.heading_add_ins": "TAMBAHAN",
"ui.heading_assemble": "PASANG",
"ui.heading_box": "KOTAK",
@@ -1793,34 +1904,6 @@
"ui.smooth_wheel_zoom": "Zum roda lancar",
"ui.smooth_wheel_zoom_tip": "Matikan untuk mengezum serta-merta bagi setiap putaran roda tetikus, tanpa gerakan susulan",
"ui.snap": "Lekap",
"ui.space_mouse_calibrate": "Tentukur…",
"ui.space_mouse_calibrate_tip": "Gerakkan penutup seperti yang diminta tetingkap, supaya SolidCraft mempelajari arah dan jarak setiap paksi tetikus 3D ini. Disimpan untuk tetikus 3D ini.",
"ui.space_mouse_calibrate_title": "Tentukur tetikus 3D",
"ui.space_mouse_calibrated": "Telah ditentukur",
"ui.space_mouse_calibration": "Penentukuran tetikus 3D",
"ui.space_mouse_calibration_done": "Selesai. Simpan untuk digunakan setiap kali tetikus 3D ini disambungkan.",
"ui.space_mouse_calibration_no_response": "Satu paksi hampir tidak bergerak, jadi tidak dapat ditentukur. Mulakan semula dan gerakkan penutup lebih jauh setiap kali.",
"ui.space_mouse_dead_zone": "Zon mati",
"ui.space_mouse_dead_zone_tip": "Sejauh mana penutup perlu bergerak sebelum pandangan bergerak, sebagai bahagian daripada julat penuhnya. Naikkan jika pandangan hanyut semasa penutup dalam keadaan rehat.",
"ui.space_mouse_forget": "Lupakan penentukuran",
"ui.space_mouse_hint": "Alih dan putar pandangan serentak; dalam lakaran, hanya sorot dan zum digunakan.",
"ui.space_mouse_navigate": "Navigasi dengan SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Tiada tetikus 3D ditemui",
"ui.space_mouse_not_calibrated": "Belum ditentukur",
"ui.space_mouse_reading": "Bacaan",
"ui.space_mouse_step_lift": "Angkat penutup ke atas, tahan, kemudian lepaskan.",
"ui.space_mouse_step_press": "Tekan penutup ke bawah, tahan, kemudian lepaskan.",
"ui.space_mouse_step_pull": "Tarik penutup ke arah anda, tahan, kemudian lepaskan.",
"ui.space_mouse_step_push": "Tolak penutup menjauhi anda, tahan, kemudian lepaskan.",
"ui.space_mouse_step_rest": "Alihkan tangan anda daripada penutup dan biarkan ia diam.",
"ui.space_mouse_step_slide_left": "Luncurkan penutup ke kiri, tahan, kemudian lepaskan.",
"ui.space_mouse_step_slide_right": "Luncurkan penutup ke kanan, tahan, kemudian lepaskan.",
"ui.space_mouse_step_tilt_back": "Condongkan penutup ke belakang (tepi depan naik), tahan, kemudian lepaskan.",
"ui.space_mouse_step_tilt_forward": "Condongkan penutup ke hadapan (tepi depan turun), tahan, kemudian lepaskan.",
"ui.space_mouse_step_tilt_left": "Condongkan penutup ke kiri (tepi kiri turun), tahan, kemudian lepaskan.",
"ui.space_mouse_step_tilt_right": "Condongkan penutup ke kanan (tepi kanan turun), tahan, kemudian lepaskan.",
"ui.space_mouse_step_twist_clockwise": "Pusing penutup ikut arah jam, tahan, kemudian lepaskan.",
"ui.space_mouse_step_twist_counterclockwise": "Pusing penutup lawan arah jam, tahan, kemudian lepaskan.",
"ui.spacing": "Selang",
"ui.spacing_2": "Selang 2",
"ui.sphere": "Sfera",
+54 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Kiezen…",
"Circular": "Cirkelvormig",
"Circumference": "Omtrek",
"Clockwise": "Met de klok mee",
"Close draft": "Concept sluiten",
"Collisions": "Botsingen",
"Colour": "Kleur",
@@ -321,6 +322,7 @@
"Search commands": "Opdrachten zoeken",
"Search this library": "Deze bibliotheek doorzoeken",
"Section": "Doorsnede",
"Section position": "Positie van de doorsnede",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Overige selecteren: houd de linkermuisknop stil op het model (of klik met rechts › Overige selecteren…) om alles onder de cursor weer te geven; klik opnieuw op dezelfde plek om het volgende item daar te selecteren. Dubbelklik met de middelste knop om naar Home te gaan (Voorkeuren › Navigatie).",
"Select bodies (a face picks its body)": "Lichamen selecteren (een vlak kiest zijn lichaam)",
"Select bodies or faces instead": "In plaats daarvan lichamen of vlakken selecteren",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft is gesloten voordat deze ontwerpen zijn opgeslagen. Hun laatste automatische opslag kan worden geopend.",
"Specialise": "Specialiseren",
"Speed": "Snelheid",
"Square": "Vierkant",
"Stand it up": "Rechtop zetten",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Rechtop zetten: een kwartslag om X, als één Verplaatsen-kenmerk dat u ongedaan kunt maken.",
"Stand this part up?": "Dit onderdeel rechtop zetten?",
"Start angle": "Beginhoek",
"Start bulge": "Beginbolling",
"Start continuity": "Begincontinuïteit",
"Step": "Stap",
@@ -411,6 +415,7 @@
"angle, height": "hoek, hoogte",
"appearance.dielectric_ior": "Diëlektrische IOR",
"appearance.metallic": "Metaalachtig",
"appearance.only_available": "Alleen beschikbare verschijningen",
"appearance.pbr_help": "Bekijk dit oppervlak in Render of een product-PNG. Geen breking of transmissie.",
"appearance.pbr_opaque": "Ondoorzichtige PBR",
"appearance.roughness": "Ruwheid",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "STEP invoegen",
"cmd.file_save_mesh": "Opslaan als mesh",
"cmd.file_slicers": "Geïnstalleerde slicers",
"cmd.frame_member": "Constructiedeel",
"cmd.frame_profile_insert": "Profiel invoegen",
"cmd.frame_profile_list": "Profielbibliotheek",
"cmd.frame_profile_properties": "Profieleigenschappen",
"cmd.inspect_accessibility": "Toegankelijkheidsanalyse",
"cmd.inspect_center_of_mass": "Zwaartepunt",
"cmd.inspect_clearance": "Speling",
@@ -568,6 +577,7 @@
"cmd.material_create": "Nieuw fysiek materiaal",
"cmd.material_delete": "Fysiek materiaal verwijderen",
"cmd.material_edit": "Fysiek materiaal bewerken",
"cmd.material_inspect": "Fysiek materiaal inspecteren",
"cmd.material_list": "Materialen weergeven",
"cmd.material_properties_directional": "Richtingsafhankelijke elasticiteitsmodulus",
"cmd.material_properties_validate": "Gegevens fysiek materiaal valideren",
@@ -1120,7 +1130,50 @@
"trace.stopped_by_contact": "Eindigt bij monster {sample}: {a} zou door {b} heen gaan",
"trace.stopped_by_loop": "Eindigt bij monster {sample}: de lus via {joint} kan niet verder sluiten",
"ui.1_degree_of_freedom": "1 vrijheidsgraad",
"ui.3d_mouse_calibrate": "Kalibreren…",
"ui.3d_mouse_calibrate_tip": "Beweeg de dop zoals het venster vraagt, zodat SolidCraft leert welke kant elke as van deze 3D-muis op gaat en hoe ver. Opgeslagen voor deze 3D-muis.",
"ui.3d_mouse_calibrate_title": "3D-muis kalibreren",
"ui.3d_mouse_calibrate_wait": "Beweeg de 3D-muis één keer, zodat SolidCraft hem ziet.",
"ui.3d_mouse_calibrated": "Gekalibreerd",
"ui.3d_mouse_calibration": "Kalibratie van 3D-muis",
"ui.3d_mouse_calibration_done": "Gereed. Sla op om het te gebruiken wanneer deze 3D-muis is aangesloten.",
"ui.3d_mouse_calibration_no_response": "Een as bewoog nauwelijks en kan dus niet worden gekalibreerd. Begin opnieuw en beweeg de dop telkens verder.",
"ui.3d_mouse_circles_help": "De cirkels tonen wat het apparaat na kalibratie stuurt. Het testmodel toont wat de weergave ermee doet, na omkeren, snelheid en asindeling.",
"ui.3d_mouse_dead_zone": "Dode zone",
"ui.3d_mouse_dead_zone_tip": "Hoe ver de dop moet bewegen voordat de weergave dat doet, als aandeel van de volledige slag. Verhoog dit als de weergave afdrijft terwijl de dop in rust is.",
"ui.3d_mouse_denied": "SolidCraft ziet een 3D-muis maar mag hem niet lezen. Zie de installatienotities voor de 3D-muis voor de udev-regel.",
"ui.3d_mouse_denied_short": "Gevonden, maar niet leesbaar",
"ui.3d_mouse_driver_fallback": "Voorlopig via spacenavd gelezen: het apparaat zelf kon niet worden geopend.",
"ui.3d_mouse_forget": "Kalibratie vergeten",
"ui.3d_mouse_ghost_left_viewport": "Viewport links",
"ui.3d_mouse_ghost_left_window": "Venster links",
"ui.3d_mouse_ghost_position": "Positie",
"ui.3d_mouse_ghost_right_viewport": "Viewport rechts",
"ui.3d_mouse_hint": "Verplaats en roteer de weergave samen; schetsen gebruiken alleen pannen en zoomen.",
"ui.3d_mouse_navigate": "Navigeren met de 3D-muis",
"ui.3d_mouse_none": "Geen 3D-muis gevonden",
"ui.3d_mouse_not_calibrated": "Niet gekalibreerd",
"ui.3d_mouse_offer": "Je 3D-muis kalibreren verhelpt verwisselde of omgekeerde assen en stelt snelheid en gevoel in.",
"ui.3d_mouse_offer_never": "Niet meer tonen",
"ui.3d_mouse_offer_swap": "Naar Hidraw wisselen (direct)",
"ui.3d_mouse_reading": "Uitlezen",
"ui.3d_mouse_reset_view": "Weergave resetten",
"ui.3d_mouse_restore": "3D-muis standaardwaarden herstellen",
"ui.3d_mouse_restore_ask": "De 3D-muisinstellingen terugzetten naar de standaardwaarden? Je kalibratie en knoppentoewijzing blijven staan.",
"ui.3d_mouse_speed": "Snelheid van 3D-muis",
"ui.3d_mouse_step_lift": "Til de dop omhoog, houd vast en laat dan los.",
"ui.3d_mouse_step_press": "Druk de dop omlaag, houd vast en laat dan los.",
"ui.3d_mouse_step_pull": "Trek de dop naar u toe, houd vast en laat dan los.",
"ui.3d_mouse_step_push": "Duw de dop van u af, houd vast en laat dan los.",
"ui.3d_mouse_step_rest": "Haal uw hand van de dop en laat hem stilstaan.",
"ui.3d_mouse_step_slide_left": "Schuif de dop naar links, houd vast en laat dan los.",
"ui.3d_mouse_step_slide_right": "Schuif de dop naar rechts, houd vast en laat dan los.",
"ui.3d_mouse_step_tilt_left": "Kantel de dop naar links (linkerrand omlaag), houd vast en laat dan los.",
"ui.3d_mouse_step_tilt_right": "Kantel de dop naar rechts (rechterrand omlaag), houd vast en laat dan los.",
"ui.3d_mouse_step_twist_clockwise": "Draai de dop met de klok mee, houd vast en laat dan los.",
"ui.3d_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
"ui.3d_mouse_waiting": "Wacht op beweging",
"ui.3d_mouse_wiggle": "Als je er een hebt aangesloten, beweeg hem even.",
"ui.3d_sketch": "3D-schets",
"ui.activate": "Activeren",
"ui.active_component": "Actief component",
@@ -1502,6 +1555,7 @@
"ui.grid_follows_view_hint": "Zijaanzichten tonen het raster op het rechtopstaande vlak dat naar hen toe gericht is in plaats van op de grond",
"ui.ground_shadow": "Grondschaduw",
"ui.groupSelected": "Selectie groeperen",
"ui.hatch": "Arcering",
"ui.heading_add_ins": "INVOEGMODULES",
"ui.heading_assemble": "SAMENSTELLEN",
"ui.heading_box": "KUB",
@@ -1792,34 +1846,6 @@
"ui.smooth_wheel_zoom": "Vloeiend zoomen met wiel",
"ui.smooth_wheel_zoom_tip": "Uitschakelen om elke wielgebeurtenis direct toe te passen, zonder uitloop",
"ui.snap": "Snap",
"ui.space_mouse_calibrate": "Kalibreren…",
"ui.space_mouse_calibrate_tip": "Beweeg de dop zoals het venster vraagt, zodat SolidCraft leert welke kant elke as van deze 3D-muis op gaat en hoe ver. Opgeslagen voor deze 3D-muis.",
"ui.space_mouse_calibrate_title": "3D-muis kalibreren",
"ui.space_mouse_calibrated": "Gekalibreerd",
"ui.space_mouse_calibration": "Kalibratie van 3D-muis",
"ui.space_mouse_calibration_done": "Gereed. Sla op om het te gebruiken wanneer deze 3D-muis is aangesloten.",
"ui.space_mouse_calibration_no_response": "Een as bewoog nauwelijks en kan dus niet worden gekalibreerd. Begin opnieuw en beweeg de dop telkens verder.",
"ui.space_mouse_dead_zone": "Dode zone",
"ui.space_mouse_dead_zone_tip": "Hoe ver de dop moet bewegen voordat de weergave dat doet, als aandeel van de volledige slag. Verhoog dit als de weergave afdrijft terwijl de dop in rust is.",
"ui.space_mouse_forget": "Kalibratie vergeten",
"ui.space_mouse_hint": "Verplaats en roteer de weergave samen; schetsen gebruiken alleen pannen en zoomen.",
"ui.space_mouse_navigate": "Navigeren met SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Geen 3D-muis gevonden",
"ui.space_mouse_not_calibrated": "Niet gekalibreerd",
"ui.space_mouse_reading": "Uitlezen",
"ui.space_mouse_step_lift": "Til de dop omhoog, houd vast en laat dan los.",
"ui.space_mouse_step_press": "Druk de dop omlaag, houd vast en laat dan los.",
"ui.space_mouse_step_pull": "Trek de dop naar u toe, houd vast en laat dan los.",
"ui.space_mouse_step_push": "Duw de dop van u af, houd vast en laat dan los.",
"ui.space_mouse_step_rest": "Haal uw hand van de dop en laat hem stilstaan.",
"ui.space_mouse_step_slide_left": "Schuif de dop naar links, houd vast en laat dan los.",
"ui.space_mouse_step_slide_right": "Schuif de dop naar rechts, houd vast en laat dan los.",
"ui.space_mouse_step_tilt_back": "Kantel de dop naar achteren (voorrand omhoog), houd vast en laat dan los.",
"ui.space_mouse_step_tilt_forward": "Kantel de dop naar voren (voorrand omlaag), houd vast en laat dan los.",
"ui.space_mouse_step_tilt_left": "Kantel de dop naar links (linkerrand omlaag), houd vast en laat dan los.",
"ui.space_mouse_step_tilt_right": "Kantel de dop naar rechts (rechterrand omlaag), houd vast en laat dan los.",
"ui.space_mouse_step_twist_clockwise": "Draai de dop met de klok mee, houd vast en laat dan los.",
"ui.space_mouse_step_twist_counterclockwise": "Draai de dop tegen de klok in, houd vast en laat dan los.",
"ui.spacing": "Tussenruimte",
"ui.spacing_2": "Tussenruimte 2",
"ui.sphere": "Bol",
+113 -53
View File
@@ -73,6 +73,7 @@
"Choose…": "Wybierz…",
"Circular": "Kołowy",
"Circumference": "Obwód",
"Clockwise": "Zgodnie z ruchem wskazówek zegara",
"Close draft": "Zamknij szkic zgłoszenia",
"Collisions": "Kolizje",
"Colour": "Kolor",
@@ -339,6 +340,7 @@
"Search commands": "Szukaj poleceń",
"Search this library": "Przeszukaj tę bibliotekę",
"Section": "Przekrój",
"Section position": "Położenie przekroju",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Wybierz inny: przytrzymaj nieruchomo lewy przycisk na modelu (lub prawy przycisk › Wybierz inny…), aby wyświetlić wszystko pod kursorem; kliknij ponownie w tym samym miejscu, aby zaznaczyć kolejny obiekt. Dwukrotne kliknięcie środkowym przyciskiem przechodzi do widoku głównego (Preferencje › Nawigacja).",
"Select bodies (a face picks its body)": "Zaznacz bryły (ściana wybiera swoją bryłę)",
"Select bodies or faces instead": "Zamiast tego zaznacz bryły lub ściany",
@@ -362,9 +364,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft zamknął się, zanim te projekty zostały zapisane. Można otworzyć ich ostatni autozapis.",
"Specialise": "Uszczegółów",
"Speed": "Prędkość",
"Square": "Kwadrat",
"Stand it up": "Postaw",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Postaw: obrót o ćwierć obrotu wokół osi X, jako jedna funkcja Przesuń, którą można cofnąć.",
"Stand this part up?": "Postawić tę część?",
"Start angle": "Kąt początkowy",
"Start bulge": "Wybrzuszenie początku",
"Start continuity": "Ciągłość początku",
"Step": "Krok",
@@ -460,6 +464,7 @@
"angle, height": "kąt, wysokość",
"appearance.dielectric_ior": "Współczynnik załamania dielektryka (IOR)",
"appearance.metallic": "Metaliczność",
"appearance.only_available": "Tylko dostępne wyglądy",
"appearance.pbr_help": "Zobacz tę powierzchnię w renderowaniu lub w obrazie PNG produktu. Bez załamania i przepuszczania światła.",
"appearance.pbr_opaque": "Nieprzezroczysty PBR",
"appearance.roughness": "Chropowatość",
@@ -578,6 +583,10 @@
"cmd.file_insert_step": "Wstaw STEP",
"cmd.file_save_mesh": "Zapisz jako siatkę",
"cmd.file_slicers": "Zainstalowane slicery",
"cmd.frame_member": "Element ramy",
"cmd.frame_profile_insert": "Wstaw profil",
"cmd.frame_profile_list": "Biblioteka profili",
"cmd.frame_profile_properties": "Właściwości profilu",
"cmd.inspect_accessibility": "Analiza dostępności",
"cmd.inspect_center_of_mass": "Środek masy",
"cmd.inspect_clearance": "Prześwit",
@@ -617,6 +626,7 @@
"cmd.material_create": "Nowy materiał fizyczny",
"cmd.material_delete": "Usuń materiał fizyczny",
"cmd.material_edit": "Edytuj materiał fizyczny",
"cmd.material_inspect": "Sprawdź materiał fizyczny",
"cmd.material_list": "Lista materiałów",
"cmd.material_properties_directional": "Kierunkowy moduł sprężystości",
"cmd.material_properties_validate": "Sprawdź dane materiału fizycznego",
@@ -1245,6 +1255,8 @@
"settings.msaa_applies_on_restart": "Wygładzanie krawędzi zadziała po ponownym uruchomieniu SolidCraft",
"settings.msaa_reset": "{setting}: {requested}× nie jest obsługiwane przez to urządzenie graficzne; ustawiono {reset}× (obsługiwane: {supported}).",
"settings.msaa_unsupported": "Nieobsługiwane przez to urządzenie graficzne",
"settings.reference_geometry_through_bodies": "Pokaż geometrię odniesienia przez bryły",
"settings.reference_geometry_through_bodies_help": "Rysuj kontury nieaktywnych szkiców i płaszczyzny odniesienia na cieniowanych bryłach",
"settings.reset_intro": "Te ustawienia nie działają na tym komputerze i uniemożliwiłyby uruchomienie SolidCraft, więc zostały zmienione:",
"settings.reset_title": "Zmieniono ustawienia",
"settings.supersampling_note": "Rysuje widok w wyższej rozdzielczości i go zmniejsza: gładsze krawędzie, linie i siatka na ekranach poniżej 4K. Kosztuje czas GPU: ok. 2,25× więcej pikseli przy 1,5× i 4× przy 2×.",
@@ -1303,7 +1315,106 @@
"trace.stopped_by_contact": "Kończy się na próbce {sample}: {a} przeszedłby przez {b}",
"trace.stopped_by_loop": "Kończy się na próbce {sample}: pętla przez {joint} nie może się bardziej zamknąć",
"ui.1_degree_of_freedom": "1 stopień swobody",
"ui.3d_mouse_axes_reassigned": "Osie są przypisane na nowo w Ustawieniach i to nadal obowiązuje razem z tą kalibracją.",
"ui.3d_mouse_button": "Przycisk {number}",
"ui.3d_mouse_button_nothing": "Nic",
"ui.3d_mouse_button_settings": "Ustawienia myszy 3D",
"ui.3d_mouse_buttons": "Przyciski",
"ui.3d_mouse_buttons_hint": "Naciśnij przycisk na myszy 3D, aby go tu znaleźć, a potem wybierz, co ma robić.",
"ui.3d_mouse_buttons_no_device": "Podłącz mysz 3D, aby ustawić, co robią jej przyciski.",
"ui.3d_mouse_buttons_reset": "Przywróć domyślne przyciski",
"ui.3d_mouse_calibrate": "Kalibruj…",
"ui.3d_mouse_calibrate_tip": "Przesuwaj nakładkę zgodnie z poleceniami okna, aby SolidCraft nauczył się kierunku i zakresu każdej osi tej myszy 3D. Zapisywane dla tej myszy 3D.",
"ui.3d_mouse_calibrate_title": "Kalibracja myszy 3D",
"ui.3d_mouse_calibrate_wait": "Rusz raz myszą 3D, aby SolidCraft ją zobaczył.",
"ui.3d_mouse_calibrated": "Skalibrowano",
"ui.3d_mouse_calibration": "Kalibracja myszy 3D",
"ui.3d_mouse_calibration_done": "Gotowe. Zapisz, aby używać kalibracji po każdym podłączeniu tej myszy 3D.",
"ui.3d_mouse_calibration_no_response": "Jedna oś prawie się nie poruszyła, więc nie można jej skalibrować. Zacznij od nowa i za każdym razem wychylaj nakładkę mocniej.",
"ui.3d_mouse_circles_help": "Koła pokazują, co urządzenie wysyła po kalibracji. Model testowy pokazuje, co widok z tym robi po odwróceniu, szybkości i przypisaniu osi.",
"ui.3d_mouse_dead_zone": "Strefa martwa",
"ui.3d_mouse_dead_zone_tip": "O ile trzeba wychylić nakładkę, zanim widok się poruszy, jako część pełnego wychylenia. Zwiększ, jeśli widok dryfuje przy nieruszanej nakładce.",
"ui.3d_mouse_denied": "SolidCraft widzi mysz 3D, ale nie ma pozwolenia, by ją odczytać. Regułę udev opisują uwagi o konfiguracji myszy 3D.",
"ui.3d_mouse_denied_short": "Znaleziono, ale nie można odczytać",
"ui.3d_mouse_driven_by": "Który skalibrowany ruch urządzenia steruje tą osią. Wybranie ruchu używanego już przez inną oś zamienia je miejscami.",
"ui.3d_mouse_driven_by_label": "sterowana przez",
"ui.3d_mouse_driver": "Sterownik myszy 3D",
"ui.3d_mouse_driver_auto": "Automatyczny",
"ui.3d_mouse_driver_direct": "Bezpośredni (hidraw)",
"ui.3d_mouse_driver_fallback": "Na razie odczyt przez spacenavd: samego urządzenia nie udało się otworzyć.",
"ui.3d_mouse_driver_only_direct": "Urządzenie jest czytane bezpośrednio; spacenavd nie działa",
"ui.3d_mouse_driver_only_spacenavd": "Odczyt przez spacenavd, jedyna dostępna droga",
"ui.3d_mouse_forget": "Zapomnij kalibrację",
"ui.3d_mouse_ghost_left_viewport": "Lewy widok",
"ui.3d_mouse_ghost_left_window": "Lewe okno",
"ui.3d_mouse_ghost_position": "Położenie",
"ui.3d_mouse_ghost_right_viewport": "Prawy widok",
"ui.3d_mouse_hint": "Przesuwaj i obracaj widok jednocześnie; w szkicach działa tylko przesuwanie i powiększanie.",
"ui.3d_mouse_kind_cap": "Powrót do środka",
"ui.3d_mouse_kind_question": "Jaki to rodzaj myszy 3D?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "Odczyt zostaje, dopóki trzymasz, jak w urządzeniu typu „Powrót do środka”.",
"ui.3d_mouse_looks_like_trackball": "Odczyt wraca, gdy tylko przestajesz ruszać, jak w trackballu.",
"ui.3d_mouse_motion_rotation_x": "Obrót X",
"ui.3d_mouse_motion_rotation_y": "Obrót Y",
"ui.3d_mouse_motion_rotation_z": "Obrót Z",
"ui.3d_mouse_motion_translation_x": "Przesunięcie X",
"ui.3d_mouse_motion_translation_y": "Przesunięcie Y",
"ui.3d_mouse_motion_translation_z": "Przesunięcie Z",
"ui.3d_mouse_navigate": "Nawiguj za pomocą myszy 3D",
"ui.3d_mouse_none": "Nie znaleziono myszy 3D",
"ui.3d_mouse_not_calibrated": "Nieskalibrowano",
"ui.3d_mouse_offer": "Kalibracja myszy 3D naprawia zamienione lub odwrócone osie oraz ustawia jej szybkość i odczucie.",
"ui.3d_mouse_offer_never": "Nie pokazuj więcej",
"ui.3d_mouse_offer_swap": "Przełącz na Hidraw (sterownik bezpośredni)",
"ui.3d_mouse_reading": "Odczyt",
"ui.3d_mouse_reset_axes": "Przywróć przypisanie osi",
"ui.3d_mouse_reset_view": "Resetuj widok",
"ui.3d_mouse_restore": "Przywróć ustawienia myszy 3D",
"ui.3d_mouse_restore_ask": "Przywrócić domyślne ustawienia myszy 3D? Kalibracja i przypisanie przycisków zostają.",
"ui.3d_mouse_section": "Mysz 3D",
"ui.3d_mouse_show_in_view": "Pokazuj mysz 3D w widoku 3D",
"ui.3d_mouse_show_in_view_tip": "Obraz twojej myszy 3D w lewym dolnym rogu, który porusza się razem z nią.",
"ui.3d_mouse_skip": "Moje urządzenie tego nie potrafi",
"ui.3d_mouse_skip_tip": "Pomija ten ruch w obu kierunkach; kalibracja go nie uwzględnia.",
"ui.3d_mouse_speed": "Czułość myszy 3D",
"ui.3d_mouse_step_lift": "Unieś nakładkę, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_press": "Wciśnij nakładkę w dół, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_pull": "Pociągnij nakładkę do siebie, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_push": "Popchnij nakładkę od siebie, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_rest": "Zdejmij dłoń z nakładki i nie ruszaj jej.",
"ui.3d_mouse_step_rest_trackball": "Zdejmij dłoń z kulki i nie ruszaj jej.",
"ui.3d_mouse_step_ring_clockwise": "Obróć pierścień przewijania o kilka kliknięć zgodnie z ruchem wskazówek zegara.",
"ui.3d_mouse_step_ring_counterclockwise": "Obróć pierścień przewijania o kilka kliknięć przeciwnie do ruchu wskazówek zegara.",
"ui.3d_mouse_step_roll_away": "Potocz kulkę kilka razy od siebie, a potem pozwól jej się zatrzymać.",
"ui.3d_mouse_step_roll_left": "Potocz kulkę kilka razy w lewo, a potem pozwól jej się zatrzymać.",
"ui.3d_mouse_step_roll_right": "Potocz kulkę kilka razy w prawo, a potem pozwól jej się zatrzymać.",
"ui.3d_mouse_step_roll_toward": "Potocz kulkę kilka razy do siebie, a potem pozwól jej się zatrzymać.",
"ui.3d_mouse_step_slide_left": "Przesuń nakładkę w lewo, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_slide_right": "Przesuń nakładkę w prawo, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_spin_clockwise": "Zakręć kulką kilka razy zgodnie z ruchem wskazówek zegara, a potem pozwól jej się zatrzymać.",
"ui.3d_mouse_step_spin_counterclockwise": "Zakręć kulką kilka razy przeciwnie do ruchu wskazówek zegara, a potem pozwól jej się zatrzymać.",
"ui.3d_mouse_step_tilt_away": "Przechyl górę nakładki od siebie (krawędź najbliżej ciebie idzie w górę), przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_tilt_left": "Przechyl nakładkę w lewo (lewa krawędź w dół), przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_tilt_right": "Przechyl nakładkę w prawo (prawa krawędź w dół), przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_tilt_toward": "Przechyl górę nakładki do siebie (krawędź najbliżej ciebie idzie w dół), przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_touch": "Połóż dłoń na nakładce, nie naciskając jej.",
"ui.3d_mouse_step_twist_clockwise": "Obróć nakładkę zgodnie z ruchem wskazówek zegara, przytrzymaj, a następnie puść.",
"ui.3d_mouse_step_twist_counterclockwise": "Obróć nakładkę przeciwnie do ruchu wskazówek zegara, przytrzymaj, a następnie puść.",
"ui.3d_mouse_switch_cap": "Kalibruj jako powrót do środka",
"ui.3d_mouse_switch_trackball": "Kalibruj jako trackball",
"ui.3d_mouse_view_axis": "Oś widoku",
"ui.3d_mouse_view_off": "Wyłączona",
"ui.3d_mouse_view_off_tip": "Profil sterownika myszy 3D wyłącza tę oś, więc jej ruch nie porusza widokiem. Wybranie tu ruchu innej osi zamienia je miejscami, a ten ruch przestaje poruszać widokiem.",
"ui.3d_mouse_view_orbit_sideways": "Orbita w lewo / prawo",
"ui.3d_mouse_view_orbit_up_down": "Orbita w górę / dół",
"ui.3d_mouse_view_pan_sideways": "Przesuwanie w lewo / prawo",
"ui.3d_mouse_view_pan_up_down": "Przesuwanie w górę / dół",
"ui.3d_mouse_view_roll": "Przechył",
"ui.3d_mouse_view_unmapped": "Oś {n}",
"ui.3d_mouse_view_zoom": "Powiększenie",
"ui.3d_mouse_waiting": "Czekam na ruch",
"ui.3d_mouse_wiggle": "Jeśli masz podłączoną, poruszaj nią chwilę.",
"ui.3d_sketch": "Szkic 3D",
"ui.activate": "Aktywuj",
"ui.active_component": "Aktywny komponent",
@@ -1535,6 +1646,7 @@
"ui.blocked": "Zablokowane",
"ui.bodies": "Bryły",
"ui.bodies_all_if_none": "Bryły (wszystkie, jeśli brak)",
"ui.bottom": "Dół",
"ui.box": "Prostopadłościan",
"ui.break": "Podziel",
"ui.browser_bodies_one": "{count} bryła",
@@ -1687,6 +1799,7 @@
"ui.grid_follows_view_hint": "Widoki boczne pokazują siatkę na pionowej płaszczyźnie zwróconej do nich zamiast na podłożu",
"ui.ground_shadow": "Cień na podłożu",
"ui.groupSelected": "Grupuj zaznaczone",
"ui.hatch": "Kreskowanie",
"ui.heading_add_ins": "DODATKI",
"ui.heading_assemble": "ZŁOŻENIE",
"ui.heading_box": "PROSTOPADŁOŚCIAN",
@@ -1984,59 +2097,6 @@
"ui.smooth_wheel_zoom": "Płynne powiększanie kółkiem",
"ui.smooth_wheel_zoom_tip": "Wyłącz, aby każdy ruch kółka działał od razu, bez wygładzania",
"ui.snap": "Przyciąganie",
"ui.space_mouse_axes_reassigned": "Osie są przypisane inaczej w Preferencjach i to przypisanie nadal działa niezależnie od tej kalibracji.",
"ui.space_mouse_calibrate": "Kalibruj…",
"ui.space_mouse_calibrate_tip": "Przesuwaj nakładkę zgodnie z poleceniami okna, aby SolidCraft nauczył się kierunku i zakresu każdej osi tej myszy 3D. Zapisywane dla tej myszy 3D.",
"ui.space_mouse_calibrate_title": "Kalibracja myszy 3D",
"ui.space_mouse_calibrated": "Skalibrowano",
"ui.space_mouse_calibration": "Kalibracja myszy 3D",
"ui.space_mouse_calibration_done": "Gotowe. Zapisz, aby używać kalibracji po każdym podłączeniu tej myszy 3D.",
"ui.space_mouse_calibration_no_response": "Jedna oś prawie się nie poruszyła, więc nie można jej skalibrować. Zacznij od nowa i za każdym razem wychylaj nakładkę mocniej.",
"ui.space_mouse_dead_zone": "Strefa martwa",
"ui.space_mouse_dead_zone_tip": "O ile trzeba wychylić nakładkę, zanim widok się poruszy, jako część pełnego wychylenia. Zwiększ, jeśli widok dryfuje przy nieruszanej nakładce.",
"ui.space_mouse_driven_by": "Który skalibrowany ruch urządzenia steruje tą osią. Wybranie ruchu używanego już przez inną oś zamienia je miejscami.",
"ui.space_mouse_driven_by_label": "sterowana przez",
"ui.space_mouse_driver": "Sterownik myszy 3D",
"ui.space_mouse_driver_auto": "Automatycznie",
"ui.space_mouse_driver_direct": "Bezpośrednio (hidraw)",
"ui.space_mouse_driver_only_direct": "Bezpośredni odczyt urządzenia; spacenavd nie działa",
"ui.space_mouse_driver_only_spacenavd": "Odczyt przez spacenavd, jedyny dostępny sposób",
"ui.space_mouse_forget": "Zapomnij kalibrację",
"ui.space_mouse_hint": "Przesuwaj i obracaj widok jednocześnie; w szkicach działa tylko przesuwanie i powiększanie.",
"ui.space_mouse_motion_rotation_x": "Obrót X",
"ui.space_mouse_motion_rotation_y": "Obrót Y",
"ui.space_mouse_motion_rotation_z": "Obrót Z",
"ui.space_mouse_motion_translation_x": "Przesunięcie X",
"ui.space_mouse_motion_translation_y": "Przesunięcie Y",
"ui.space_mouse_motion_translation_z": "Przesunięcie Z",
"ui.space_mouse_navigate": "Nawiguj za pomocą SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Nie znaleziono myszy 3D",
"ui.space_mouse_not_calibrated": "Nieskalibrowano",
"ui.space_mouse_reading": "Odczyt",
"ui.space_mouse_reset_axes": "Resetuj przypisanie osi",
"ui.space_mouse_step_lift": "Unieś nakładkę, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_press": "Wciśnij nakładkę w dół, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_pull": "Pociągnij nakładkę do siebie, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_push": "Popchnij nakładkę od siebie, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_rest": "Zdejmij dłoń z nakładki i nie ruszaj jej.",
"ui.space_mouse_step_slide_left": "Przesuń nakładkę w lewo, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_slide_right": "Przesuń nakładkę w prawo, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_tilt_back": "Przechyl nakładkę do tyłu (przednia krawędź w górę), przytrzymaj, a następnie puść.",
"ui.space_mouse_step_tilt_forward": "Przechyl nakładkę do przodu (przednia krawędź w dół), przytrzymaj, a następnie puść.",
"ui.space_mouse_step_tilt_left": "Przechyl nakładkę w lewo (lewa krawędź w dół), przytrzymaj, a następnie puść.",
"ui.space_mouse_step_tilt_right": "Przechyl nakładkę w prawo (prawa krawędź w dół), przytrzymaj, a następnie puść.",
"ui.space_mouse_step_twist_clockwise": "Obróć nakładkę zgodnie z ruchem wskazówek zegara, przytrzymaj, a następnie puść.",
"ui.space_mouse_step_twist_counterclockwise": "Obróć nakładkę przeciwnie do ruchu wskazówek zegara, przytrzymaj, a następnie puść.",
"ui.space_mouse_view_axis": "Oś widoku",
"ui.space_mouse_view_off": "Wyłączona",
"ui.space_mouse_view_off_tip": "Profil sterownika myszy 3D wyłącza tę oś, więc jej ruch nie porusza widokiem. Wybranie tutaj ruchu innej osi zamienia je miejscami, a ten ruch przestaje poruszać widokiem.",
"ui.space_mouse_view_orbit_sideways": "Obracanie w lewo / w prawo",
"ui.space_mouse_view_orbit_up_down": "Obracanie w górę / w dół",
"ui.space_mouse_view_pan_sideways": "Przesuwanie w lewo / w prawo",
"ui.space_mouse_view_pan_up_down": "Przesuwanie w górę / w dół",
"ui.space_mouse_view_roll": "Przechylenie",
"ui.space_mouse_view_unmapped": "Oś {n}",
"ui.space_mouse_view_zoom": "Powiększanie",
"ui.spacing": "Odstęp",
"ui.spacing_2": "Odstęp 2",
"ui.sphere": "Sfera",
+118 -53
View File
@@ -72,6 +72,7 @@
"Choose…": "Escolher…",
"Circular": "Circular",
"Circumference": "Circunferência",
"Clockwise": "Horário",
"Close draft": "Fechar rascunho",
"Collisions": "Colisões",
"Colour": "Cor",
@@ -322,6 +323,7 @@
"Search commands": "Pesquisar comandos",
"Search this library": "Pesquisar nesta biblioteca",
"Section": "Seção",
"Section position": "Posição da seção",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Selecionar outro: mantenha o botão esquerdo parado sobre o modelo (ou clique com o botão direito › Selecionar outro…) para listar tudo sob o cursor; clique de novo no mesmo ponto para selecionar o próximo item ali. Clique duas vezes com o botão do meio para ir ao Início (Preferências › Navegação).",
"Select bodies (a face picks its body)": "Selecione corpos (uma face seleciona seu corpo)",
"Select bodies or faces instead": "Selecionar corpos ou faces em vez disso",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "O SolidCraft fechou antes de estes projetos serem salvos. O último salvamento automático deles pode ser aberto.",
"Specialise": "Especializar",
"Speed": "Velocidade",
"Square": "Quadrado",
"Stand it up": "Colocar em pé",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Colocar em pé: um quarto de volta em torno de X, como um recurso Mover que pode ser desfeito.",
"Stand this part up?": "Colocar esta peça em pé?",
"Start angle": "Ângulo inicial",
"Start bulge": "Abaulamento inicial",
"Start continuity": "Continuidade inicial",
"Step": "Passo",
@@ -412,6 +416,7 @@
"angle, height": "ângulo, altura",
"appearance.dielectric_ior": "IOR dielétrico",
"appearance.metallic": "Metálico",
"appearance.only_available": "Somente aparências disponíveis",
"appearance.pbr_help": "Veja esta superfície em Renderizar ou em um PNG de produto. Sem refração nem transmissão.",
"appearance.pbr_opaque": "PBR opaco",
"appearance.roughness": "Rugosidade",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Inserir STEP",
"cmd.file_save_mesh": "Salvar como malha",
"cmd.file_slicers": "Fatiadores instalados",
"cmd.frame_member": "Membro estrutural",
"cmd.frame_profile_insert": "Inserir perfil",
"cmd.frame_profile_list": "Biblioteca de perfis",
"cmd.frame_profile_properties": "Propriedades do perfil",
"cmd.inspect_accessibility": "Análise de acessibilidade",
"cmd.inspect_center_of_mass": "Centro de massa",
"cmd.inspect_clearance": "Folga",
@@ -569,6 +578,7 @@
"cmd.material_create": "Novo material físico",
"cmd.material_delete": "Excluir material físico",
"cmd.material_edit": "Editar material físico",
"cmd.material_inspect": "Inspecionar material físico",
"cmd.material_list": "Listar materiais",
"cmd.material_properties_directional": "Módulo de elasticidade direcional",
"cmd.material_properties_validate": "Validar dados do material físico",
@@ -755,12 +765,19 @@
"cmd.timeline_suppress": "Suprimir recurso",
"cmd.view_ambient_occlusion": "Oclusão de ambiente",
"cmd.view_apply": "Restaurar vista nomeada",
"cmd.view_back": "Vista traseira",
"cmd.view_bottom": "Vista inferior",
"cmd.view_delete": "Excluir vista nomeada",
"cmd.view_front": "Vista frontal",
"cmd.view_graphics_preset": "Predefinição gráfica",
"cmd.view_isometric": "Vista isométrica",
"cmd.view_left": "Vista esquerda",
"cmd.view_list": "Listar vistas nomeadas",
"cmd.view_navigate": "Navegar na vista",
"cmd.view_rename": "Renomear vista nomeada",
"cmd.view_right": "Vista direita",
"cmd.view_save": "Nova vista nomeada",
"cmd.view_top": "Vista superior",
"color.copy": "Clique para copiar os valores da cor",
"color.edit": "Clique para editar a cor",
"color.gamma": "Mostrando valores de cor no espaço gama 0-255",
@@ -1121,7 +1138,106 @@
"trace.stopped_by_contact": "Termina na amostra {sample}: {a} atravessaria {b}",
"trace.stopped_by_loop": "Termina na amostra {sample}: o laço por {joint} não pode fechar mais",
"ui.1_degree_of_freedom": "1 grau de liberdade",
"ui.3d_mouse_axes_reassigned": "Os eixos estão reatribuídos nas Preferências, e isso continua valendo sobre esta calibração.",
"ui.3d_mouse_button": "Botão {number}",
"ui.3d_mouse_button_nothing": "Nada",
"ui.3d_mouse_button_settings": "Configurações do mouse 3D",
"ui.3d_mouse_buttons": "Botões",
"ui.3d_mouse_buttons_hint": "Aperte um botão do seu mouse 3D para encontrá-lo aqui e escolha o que ele faz.",
"ui.3d_mouse_buttons_no_device": "Conecte um mouse 3D para escolher o que os botões fazem.",
"ui.3d_mouse_buttons_reset": "Restaurar os botões padrão",
"ui.3d_mouse_calibrate": "Calibrar…",
"ui.3d_mouse_calibrate_tip": "Mova a tampa como a janela pedir, para que o SolidCraft aprenda para onde e até onde vai cada eixo deste mouse 3D. Salvo para este mouse 3D.",
"ui.3d_mouse_calibrate_title": "Calibrar mouse 3D",
"ui.3d_mouse_calibrate_wait": "Mova o mouse 3D uma vez para o SolidCraft vê-lo.",
"ui.3d_mouse_calibrated": "Calibrado",
"ui.3d_mouse_calibration": "Calibração do mouse 3D",
"ui.3d_mouse_calibration_done": "Pronto. Salve para usá-la sempre que este mouse 3D estiver conectado.",
"ui.3d_mouse_calibration_no_response": "Um eixo quase não se moveu, então não dá para calibrá-lo. Recomece e mova a tampa mais longe a cada vez.",
"ui.3d_mouse_circles_help": "Os círculos mostram o que o dispositivo envia após a calibração. O modelo de teste mostra o que a vista faz com isso, após inversão, velocidade e atribuição de eixos.",
"ui.3d_mouse_dead_zone": "Zona morta",
"ui.3d_mouse_dead_zone_tip": "Quanto a tampa precisa se mover antes de a vista se mover, como fração do curso completo. Aumente se a vista se mexer com a tampa parada.",
"ui.3d_mouse_denied": "O SolidCraft vê um mouse 3D, mas não tem permissão para lê-lo. A regra udev está nas notas de configuração do mouse 3D.",
"ui.3d_mouse_denied_short": "Encontrado, mas não legível",
"ui.3d_mouse_driven_by": "Qual movimento calibrado do dispositivo controla este eixo. Escolher um que outro eixo já usa troca os dois.",
"ui.3d_mouse_driven_by_label": "controlado por",
"ui.3d_mouse_driver": "Driver do mouse 3D",
"ui.3d_mouse_driver_auto": "Automático",
"ui.3d_mouse_driver_direct": "Direto (hidraw)",
"ui.3d_mouse_driver_fallback": "Lendo por enquanto pelo spacenavd: não foi possível abrir o dispositivo em si.",
"ui.3d_mouse_driver_only_direct": "Lendo o dispositivo diretamente; o spacenavd não está em execução",
"ui.3d_mouse_driver_only_spacenavd": "Lendo pelo spacenavd, a única opção disponível",
"ui.3d_mouse_forget": "Esquecer calibração",
"ui.3d_mouse_ghost_left_viewport": "Viewport à esquerda",
"ui.3d_mouse_ghost_left_window": "Janela à esquerda",
"ui.3d_mouse_ghost_position": "Posição",
"ui.3d_mouse_ghost_right_viewport": "Viewport à direita",
"ui.3d_mouse_hint": "Move e gira a vista juntos; esboços usam apenas deslocamento e zoom.",
"ui.3d_mouse_kind_cap": "Retorno ao centro",
"ui.3d_mouse_kind_question": "Que tipo de mouse 3D é?",
"ui.3d_mouse_kind_trackball": "Trackball",
"ui.3d_mouse_looks_like_cap": "O valor se mantém enquanto você segura, como em um dispositivo do tipo “Retorno ao centro”.",
"ui.3d_mouse_looks_like_trackball": "O valor volta assim que você para de mover, como em um trackball.",
"ui.3d_mouse_motion_rotation_x": "Rotação X",
"ui.3d_mouse_motion_rotation_y": "Rotação Y",
"ui.3d_mouse_motion_rotation_z": "Rotação Z",
"ui.3d_mouse_motion_translation_x": "Translação X",
"ui.3d_mouse_motion_translation_y": "Translação Y",
"ui.3d_mouse_motion_translation_z": "Translação Z",
"ui.3d_mouse_navigate": "Navegar com o mouse 3D",
"ui.3d_mouse_none": "Nenhum mouse 3D encontrado",
"ui.3d_mouse_not_calibrated": "Não calibrado",
"ui.3d_mouse_offer": "Calibrar o mouse 3D corrige eixos trocados ou invertidos e ajusta a velocidade e a sensação.",
"ui.3d_mouse_offer_never": "Não mostrar novamente",
"ui.3d_mouse_offer_swap": "Mudar para Hidraw (driver direto)",
"ui.3d_mouse_reading": "Leitura",
"ui.3d_mouse_reset_axes": "Redefinir atribuição de eixos",
"ui.3d_mouse_reset_view": "Redefinir visualização",
"ui.3d_mouse_restore": "Restaurar padrões do mouse 3D",
"ui.3d_mouse_restore_ask": "Voltar as configurações do mouse 3D aos padrões? A calibração e o mapeamento dos botões continuam.",
"ui.3d_mouse_section": "Mouse 3D",
"ui.3d_mouse_show_in_view": "Mostrar o mouse 3D na vista 3D",
"ui.3d_mouse_show_in_view_tip": "Uma imagem do seu mouse 3D no canto inferior esquerdo, que se move quando você o move.",
"ui.3d_mouse_skip": "Meu dispositivo não faz isso",
"ui.3d_mouse_skip_tip": "Pula este movimento nos dois sentidos; a calibração o deixa de fora.",
"ui.3d_mouse_speed": "Velocidade do mouse 3D",
"ui.3d_mouse_step_lift": "Levante a tampa, segure e solte.",
"ui.3d_mouse_step_press": "Pressione a tampa para baixo, segure e solte.",
"ui.3d_mouse_step_pull": "Puxe a tampa na sua direção, segure e solte.",
"ui.3d_mouse_step_push": "Empurre a tampa para longe de você, segure e solte.",
"ui.3d_mouse_step_rest": "Tire a mão da tampa e deixe-a parada.",
"ui.3d_mouse_step_rest_trackball": "Tire a mão da bola e deixe-a parada.",
"ui.3d_mouse_step_ring_clockwise": "Gire o anel de rolagem alguns cliques no sentido horário.",
"ui.3d_mouse_step_ring_counterclockwise": "Gire o anel de rolagem alguns cliques no sentido anti-horário.",
"ui.3d_mouse_step_roll_away": "Role a bola para longe de você algumas vezes e deixe-a parar.",
"ui.3d_mouse_step_roll_left": "Role a bola para a esquerda algumas vezes e deixe-a parar.",
"ui.3d_mouse_step_roll_right": "Role a bola para a direita algumas vezes e deixe-a parar.",
"ui.3d_mouse_step_roll_toward": "Role a bola na sua direção algumas vezes e deixe-a parar.",
"ui.3d_mouse_step_slide_left": "Deslize a tampa para a esquerda, segure e solte.",
"ui.3d_mouse_step_slide_right": "Deslize a tampa para a direita, segure e solte.",
"ui.3d_mouse_step_spin_clockwise": "Gire a bola no sentido horário algumas vezes e deixe-a parar.",
"ui.3d_mouse_step_spin_counterclockwise": "Gire a bola no sentido anti-horário algumas vezes e deixe-a parar.",
"ui.3d_mouse_step_tilt_away": "Incline o topo da tampa para longe de você (a borda mais próxima sobe), segure e solte.",
"ui.3d_mouse_step_tilt_left": "Incline a tampa para a esquerda (borda esquerda para baixo), segure e solte.",
"ui.3d_mouse_step_tilt_right": "Incline a tampa para a direita (borda direita para baixo), segure e solte.",
"ui.3d_mouse_step_tilt_toward": "Incline o topo da tampa na sua direção (a borda mais próxima desce), segure e solte.",
"ui.3d_mouse_step_touch": "Apoie a mão na tampa sem empurrá-la.",
"ui.3d_mouse_step_twist_clockwise": "Gire a tampa no sentido horário, segure e solte.",
"ui.3d_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
"ui.3d_mouse_switch_cap": "Calibrar como retorno ao centro",
"ui.3d_mouse_switch_trackball": "Calibrar como trackball",
"ui.3d_mouse_view_axis": "Eixo da vista",
"ui.3d_mouse_view_off": "Desligado",
"ui.3d_mouse_view_off_tip": "O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
"ui.3d_mouse_view_orbit_sideways": "Orbitar esquerda / direita",
"ui.3d_mouse_view_orbit_up_down": "Orbitar cima / baixo",
"ui.3d_mouse_view_pan_sideways": "Deslocar esquerda / direita",
"ui.3d_mouse_view_pan_up_down": "Deslocar cima / baixo",
"ui.3d_mouse_view_roll": "Girar a vista",
"ui.3d_mouse_view_unmapped": "Eixo {n}",
"ui.3d_mouse_view_zoom": "Zoom",
"ui.3d_mouse_waiting": "Aguardando movimento",
"ui.3d_mouse_wiggle": "Se você tiver um conectado, mexa nele um pouco.",
"ui.3d_sketch": "Esboço 3D",
"ui.activate": "Ativar",
"ui.active_component": "Componente ativo",
@@ -1351,6 +1467,7 @@
"ui.blocked": "Bloqueado",
"ui.bodies": "Corpos",
"ui.bodies_all_if_none": "Corpos (todos se nenhum)",
"ui.bottom": "Base",
"ui.box": "Caixa",
"ui.break": "Quebrar",
"ui.browser_bodies_one": "{count} corpo",
@@ -1503,6 +1620,7 @@
"ui.grid_follows_view_hint": "As vistas laterais mostram a grade no plano vertical voltado para elas, em vez do solo",
"ui.ground_shadow": "Sombra no chão",
"ui.groupSelected": "Agrupar seleção",
"ui.hatch": "Hachura",
"ui.heading_add_ins": "COMPLEMENTOS",
"ui.heading_assemble": "MONTAR",
"ui.heading_box": "CAIXA",
@@ -1793,59 +1911,6 @@
"ui.smooth_wheel_zoom": "Zoom suave com a roda",
"ui.smooth_wheel_zoom_tip": "Desative para aplicar cada evento da roda na hora, sem rastro de suavização",
"ui.snap": "Encaixe",
"ui.space_mouse_axes_reassigned": "Os eixos estão reatribuídos nas Preferências, e isso continua valendo sobre esta calibração.",
"ui.space_mouse_calibrate": "Calibrar…",
"ui.space_mouse_calibrate_tip": "Mova a tampa como a janela pedir, para que o SolidCraft aprenda para onde e até onde vai cada eixo deste mouse 3D. Salvo para este mouse 3D.",
"ui.space_mouse_calibrate_title": "Calibrar mouse 3D",
"ui.space_mouse_calibrated": "Calibrado",
"ui.space_mouse_calibration": "Calibração do mouse 3D",
"ui.space_mouse_calibration_done": "Pronto. Salve para usá-la sempre que este mouse 3D estiver conectado.",
"ui.space_mouse_calibration_no_response": "Um eixo quase não se moveu, então não dá para calibrá-lo. Recomece e mova a tampa mais longe a cada vez.",
"ui.space_mouse_dead_zone": "Zona morta",
"ui.space_mouse_dead_zone_tip": "Quanto a tampa precisa se mover antes de a vista se mover, como fração do curso completo. Aumente se a vista se mexer com a tampa parada.",
"ui.space_mouse_driven_by": "Qual movimento calibrado do dispositivo controla este eixo. Escolher um que outro eixo já usa troca os dois.",
"ui.space_mouse_driven_by_label": "controlado por",
"ui.space_mouse_driver": "Driver do mouse 3D",
"ui.space_mouse_driver_auto": "Automático",
"ui.space_mouse_driver_direct": "Direto (hidraw)",
"ui.space_mouse_driver_only_direct": "Lendo o dispositivo diretamente; o spacenavd não está em execução",
"ui.space_mouse_driver_only_spacenavd": "Lendo pelo spacenavd, a única opção disponível",
"ui.space_mouse_forget": "Esquecer calibração",
"ui.space_mouse_hint": "Move e gira a vista juntos; esboços usam apenas deslocamento e zoom.",
"ui.space_mouse_motion_rotation_x": "Rotação X",
"ui.space_mouse_motion_rotation_y": "Rotação Y",
"ui.space_mouse_motion_rotation_z": "Rotação Z",
"ui.space_mouse_motion_translation_x": "Translação X",
"ui.space_mouse_motion_translation_y": "Translação Y",
"ui.space_mouse_motion_translation_z": "Translação Z",
"ui.space_mouse_navigate": "Navegar com SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Nenhum mouse 3D encontrado",
"ui.space_mouse_not_calibrated": "Não calibrado",
"ui.space_mouse_reading": "Leitura",
"ui.space_mouse_reset_axes": "Redefinir atribuição de eixos",
"ui.space_mouse_step_lift": "Levante a tampa, segure e solte.",
"ui.space_mouse_step_press": "Pressione a tampa para baixo, segure e solte.",
"ui.space_mouse_step_pull": "Puxe a tampa na sua direção, segure e solte.",
"ui.space_mouse_step_push": "Empurre a tampa para longe de você, segure e solte.",
"ui.space_mouse_step_rest": "Tire a mão da tampa e deixe-a parada.",
"ui.space_mouse_step_slide_left": "Deslize a tampa para a esquerda, segure e solte.",
"ui.space_mouse_step_slide_right": "Deslize a tampa para a direita, segure e solte.",
"ui.space_mouse_step_tilt_back": "Incline a tampa para trás (borda da frente para cima), segure e solte.",
"ui.space_mouse_step_tilt_forward": "Incline a tampa para a frente (borda da frente para baixo), segure e solte.",
"ui.space_mouse_step_tilt_left": "Incline a tampa para a esquerda (borda esquerda para baixo), segure e solte.",
"ui.space_mouse_step_tilt_right": "Incline a tampa para a direita (borda direita para baixo), segure e solte.",
"ui.space_mouse_step_twist_clockwise": "Gire a tampa no sentido horário, segure e solte.",
"ui.space_mouse_step_twist_counterclockwise": "Gire a tampa no sentido anti-horário, segure e solte.",
"ui.space_mouse_view_axis": "Eixo da vista",
"ui.space_mouse_view_off": "Desligado",
"ui.space_mouse_view_off_tip": "O perfil do driver do mouse 3D desliga este eixo, então o movimento dele não move a vista. Escolher aqui o movimento de outro eixo troca os dois, e esse movimento deixa de mover a vista.",
"ui.space_mouse_view_orbit_sideways": "Orbitar esquerda / direita",
"ui.space_mouse_view_orbit_up_down": "Orbitar cima / baixo",
"ui.space_mouse_view_pan_sideways": "Deslocar esquerda / direita",
"ui.space_mouse_view_pan_up_down": "Deslocar cima / baixo",
"ui.space_mouse_view_roll": "Girar a vista",
"ui.space_mouse_view_unmapped": "Eixo {n}",
"ui.space_mouse_view_zoom": "Zoom",
"ui.spacing": "Espaçamento",
"ui.spacing_2": "Espaçamento 2",
"ui.sphere": "Esfera",
+54 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Alegeți…",
"Circular": "Circular",
"Circumference": "Circumferință",
"Clockwise": "În sens orar",
"Close draft": "Închide ciorna",
"Collisions": "Coliziuni",
"Colour": "Culoare",
@@ -321,6 +322,7 @@
"Search commands": "Caută comenzi",
"Search this library": "Caută în această bibliotecă",
"Section": "Secțiune",
"Section position": "Poziția secțiunii",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Selectează altul: țineți apăsat butonul stâng nemișcat pe model (sau clic dreapta › Selectează altul…) pentru a lista tot ce se află sub cursor; faceți clic din nou în același loc pentru a selecta următorul element. Dublu clic pe butonul din mijloc pentru a merge Acasă (Preferințe › Navigare).",
"Select bodies (a face picks its body)": "Selectați corpuri (o față alege corpul ei)",
"Select bodies or faces instead": "Selectați în schimb corpuri sau fețe",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft s-a închis înainte ca aceste proiecte să fie salvate. Ultima lor salvare automată poate fi deschisă.",
"Specialise": "Specializează",
"Speed": "Viteză",
"Square": "Pătrat",
"Stand it up": "Ridică-l în picioare",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Ridică-l în picioare: un sfert de rotație în jurul lui X, ca o singură operație de mutare ce poate fi anulată.",
"Stand this part up?": "Ridicați această piesă în picioare?",
"Start angle": "Unghi de început",
"Start bulge": "Bombare inițială",
"Start continuity": "Continuitate inițială",
"Step": "Pas",
@@ -411,6 +415,7 @@
"angle, height": "unghi, înălțime",
"appearance.dielectric_ior": "IOR dielectric",
"appearance.metallic": "Metalic",
"appearance.only_available": "Doar aspecte disponibile",
"appearance.pbr_help": "Vizualizați această suprafață în Randare sau într-un PNG de produs. Fără refracție sau transmisie.",
"appearance.pbr_opaque": "PBR opac",
"appearance.roughness": "Rugozitate",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "Inserează STEP",
"cmd.file_save_mesh": "Salvează ca plasă",
"cmd.file_slicers": "Slicere instalate",
"cmd.frame_member": "Element de cadru",
"cmd.frame_profile_insert": "Inserare profil",
"cmd.frame_profile_list": "Bibliotecă de profile",
"cmd.frame_profile_properties": "Proprietăți profil",
"cmd.inspect_accessibility": "Analiza accesibilității",
"cmd.inspect_center_of_mass": "Centru de masă",
"cmd.inspect_clearance": "Joc",
@@ -568,6 +577,7 @@
"cmd.material_create": "Material fizic nou",
"cmd.material_delete": "Șterge materialul fizic",
"cmd.material_edit": "Editează materialul fizic",
"cmd.material_inspect": "Inspectează materialul fizic",
"cmd.material_list": "Listează materialele",
"cmd.material_properties_directional": "Modul de elasticitate direcțional",
"cmd.material_properties_validate": "Validează datele materialului fizic",
@@ -1120,7 +1130,50 @@
"trace.stopped_by_contact": "Se termină la eșantionul {sample}: {a} ar trece prin {b}",
"trace.stopped_by_loop": "Se termină la eșantionul {sample}: bucla prin {joint} nu se mai poate închide",
"ui.1_degree_of_freedom": "1 grad de libertate",
"ui.3d_mouse_calibrate": "Calibrează…",
"ui.3d_mouse_calibrate_tip": "Mișcați capacul așa cum cere fereastra, ca SolidCraft să învețe în ce direcție merge fiecare axă a acestui mouse 3D și cât de departe. Salvat pentru acest mouse 3D.",
"ui.3d_mouse_calibrate_title": "Calibrează mouse-ul 3D",
"ui.3d_mouse_calibrate_wait": "Mișcă o dată mouse-ul 3D ca SolidCraft să îl vadă.",
"ui.3d_mouse_calibrated": "Calibrat",
"ui.3d_mouse_calibration": "Calibrarea mouse-ului 3D",
"ui.3d_mouse_calibration_done": "Gata. Salvați-o pentru a o folosi ori de câte ori acest mouse 3D este conectat.",
"ui.3d_mouse_calibration_no_response": "O axă abia s-a mișcat, deci nu poate fi calibrată. Reîncepeți și mișcați capacul mai mult de fiecare dată.",
"ui.3d_mouse_circles_help": "Cercurile arată ce trimite dispozitivul după calibrare. Modelul de test arată ce face vizualizarea cu acestea, după inversare, viteză și atribuirea axelor.",
"ui.3d_mouse_dead_zone": "Zonă moartă",
"ui.3d_mouse_dead_zone_tip": "Cât trebuie să se miște capacul înainte ca vizualizarea să se miște, ca fracțiune din cursa completă. Măriți-o dacă vizualizarea derivă cât capacul este în repaus.",
"ui.3d_mouse_denied": "SolidCraft vede un mouse 3D, dar nu are voie să îl citească. Regula udev este în notele de configurare a mouse-ului 3D.",
"ui.3d_mouse_denied_short": "Găsit, dar nu poate fi citit",
"ui.3d_mouse_driver_fallback": "Deocamdată se citește prin spacenavd: dispozitivul în sine nu a putut fi deschis.",
"ui.3d_mouse_forget": "Uită calibrarea",
"ui.3d_mouse_ghost_left_viewport": "Viewport stânga",
"ui.3d_mouse_ghost_left_window": "Fereastră stânga",
"ui.3d_mouse_ghost_position": "Poziție",
"ui.3d_mouse_ghost_right_viewport": "Viewport dreapta",
"ui.3d_mouse_hint": "Mută și rotește vizualizarea împreună; schițele folosesc doar panoramare și zoom.",
"ui.3d_mouse_navigate": "Navighează cu mouse-ul 3D",
"ui.3d_mouse_none": "Niciun mouse 3D găsit",
"ui.3d_mouse_not_calibrated": "Necalibrat",
"ui.3d_mouse_offer": "Calibrarea mouse-ului 3D corectează axele inversate sau schimbate între ele și îi reglează viteza și senzația.",
"ui.3d_mouse_offer_never": "Nu mai afișa",
"ui.3d_mouse_offer_swap": "Treci la Hidraw (driver direct)",
"ui.3d_mouse_reading": "Valoare citită",
"ui.3d_mouse_reset_view": "Resetează vizualizarea",
"ui.3d_mouse_restore": "Restaurează implicitele mouse-ului 3D",
"ui.3d_mouse_restore_ask": "Revii la valorile implicite ale setărilor mouse-ului 3D? Calibrarea și atribuirea butoanelor rămân.",
"ui.3d_mouse_speed": "Viteza mouse-ului 3D",
"ui.3d_mouse_step_lift": "Ridicați capacul în sus, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_press": "Apăsați capacul în jos, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_pull": "Trageți capacul spre dumneavoastră, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_push": "Împingeți capacul de la dumneavoastră, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_rest": "Luați mâna de pe capac și lăsați-l nemișcat.",
"ui.3d_mouse_step_slide_left": "Glisați capacul spre stânga, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_slide_right": "Glisați capacul spre dreapta, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_tilt_left": "Înclinați capacul spre stânga (marginea stângă în jos), țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_tilt_right": "Înclinați capacul spre dreapta (marginea dreaptă în jos), țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_twist_clockwise": "Răsuciți capacul în sensul acelor de ceasornic, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_step_twist_counterclockwise": "Răsuciți capacul în sens invers acelor de ceasornic, țineți-l, apoi eliberați-l.",
"ui.3d_mouse_waiting": "Se așteaptă mișcare",
"ui.3d_mouse_wiggle": "Dacă ai unul conectat, mișcă-l puțin.",
"ui.3d_sketch": "Schiță 3D",
"ui.activate": "Activează",
"ui.active_component": "Componentă activă",
@@ -1502,6 +1555,7 @@
"ui.grid_follows_view_hint": "Vederile laterale afișează grila pe planul vertical orientat spre ele, în loc de sol",
"ui.ground_shadow": "Umbra la sol",
"ui.groupSelected": "Grupează selecția",
"ui.hatch": "Hașură",
"ui.heading_add_ins": "EXTENSII",
"ui.heading_assemble": "ASAMBLARE",
"ui.heading_box": "CUTIE",
@@ -1792,34 +1846,6 @@
"ui.smooth_wheel_zoom": "Zoom lin cu rotița",
"ui.smooth_wheel_zoom_tip": "Dezactivați pentru a aplica imediat fiecare eveniment al rotiței, fără coadă de netezire",
"ui.snap": "Agățare",
"ui.space_mouse_calibrate": "Calibrează…",
"ui.space_mouse_calibrate_tip": "Mișcați capacul așa cum cere fereastra, ca SolidCraft să învețe în ce direcție merge fiecare axă a acestui mouse 3D și cât de departe. Salvat pentru acest mouse 3D.",
"ui.space_mouse_calibrate_title": "Calibrează mouse-ul 3D",
"ui.space_mouse_calibrated": "Calibrat",
"ui.space_mouse_calibration": "Calibrarea mouse-ului 3D",
"ui.space_mouse_calibration_done": "Gata. Salvați-o pentru a o folosi ori de câte ori acest mouse 3D este conectat.",
"ui.space_mouse_calibration_no_response": "O axă abia s-a mișcat, deci nu poate fi calibrată. Reîncepeți și mișcați capacul mai mult de fiecare dată.",
"ui.space_mouse_dead_zone": "Zonă moartă",
"ui.space_mouse_dead_zone_tip": "Cât trebuie să se miște capacul înainte ca vizualizarea să se miște, ca fracțiune din cursa completă. Măriți-o dacă vizualizarea derivă cât capacul este în repaus.",
"ui.space_mouse_forget": "Uită calibrarea",
"ui.space_mouse_hint": "Mută și rotește vizualizarea împreună; schițele folosesc doar panoramare și zoom.",
"ui.space_mouse_navigate": "Navighează cu SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Niciun mouse 3D găsit",
"ui.space_mouse_not_calibrated": "Necalibrat",
"ui.space_mouse_reading": "Valoare citită",
"ui.space_mouse_step_lift": "Ridicați capacul în sus, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_press": "Apăsați capacul în jos, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_pull": "Trageți capacul spre dumneavoastră, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_push": "Împingeți capacul de la dumneavoastră, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_rest": "Luați mâna de pe capac și lăsați-l nemișcat.",
"ui.space_mouse_step_slide_left": "Glisați capacul spre stânga, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_slide_right": "Glisați capacul spre dreapta, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_tilt_back": "Înclinați capacul spre spate (marginea din față în sus), țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_tilt_forward": "Înclinați capacul spre față (marginea din față în jos), țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_tilt_left": "Înclinați capacul spre stânga (marginea stângă în jos), țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_tilt_right": "Înclinați capacul spre dreapta (marginea dreaptă în jos), țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_twist_clockwise": "Răsuciți capacul în sensul acelor de ceasornic, țineți-l, apoi eliberați-l.",
"ui.space_mouse_step_twist_counterclockwise": "Răsuciți capacul în sens invers acelor de ceasornic, țineți-l, apoi eliberați-l.",
"ui.spacing": "Spațiere",
"ui.spacing_2": "Spațierea 2",
"ui.sphere": "Sferă",
+111 -53
View File
@@ -72,6 +72,7 @@
"Choose…": "Выбрать…",
"Circular": "Круговой",
"Circumference": "Окружность",
"Clockwise": "По часовой стрелке",
"Close draft": "Закрыть анализ уклонов",
"Collisions": "Столкновения",
"Colour": "Цвет",
@@ -321,6 +322,7 @@
"Search commands": "Поиск команд",
"Search this library": "Поиск в этой библиотеке",
"Section": "Сечение",
"Section position": "Положение сечения",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Выбрать другое: удерживайте левую кнопку на модели (или правый щелчок › Выбрать другое…), чтобы увидеть всё под курсором; повторный щелчок в том же месте выбирает следующий объект. Двойной щелчок средней кнопкой — «Главная» (Настройки › Навигация).",
"Select bodies (a face picks its body)": "Выберите тела (выбор грани выбирает её тело)",
"Select bodies or faces instead": "Вместо этого выберите тела или грани",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft закрылся до сохранения этих дизайнов. Можно открыть последнюю автосохранённую версию.",
"Specialise": "Специализировать",
"Speed": "Скорость",
"Square": "Квадрат",
"Stand it up": "Поставить вертикально",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Поставить вертикально: четверть оборота вокруг X одной операцией «Переместить», которую можно отменить.",
"Stand this part up?": "Поставить эту деталь вертикально?",
"Start angle": "Начальный угол",
"Start bulge": "Выпуклость в начале",
"Start continuity": "Непрерывность в начале",
"Step": "Шаг",
@@ -411,6 +415,7 @@
"angle, height": "угол, высота",
"appearance.dielectric_ior": "Показатель преломления диэлектрика",
"appearance.metallic": "Металличность",
"appearance.only_available": "Только доступные внешние виды",
"appearance.pbr_help": "Просмотр поверхности во вкладке Рендер или на PNG изделия. Без преломления и пропускания света.",
"appearance.pbr_opaque": "Непрозрачный PBR",
"appearance.roughness": "Шероховатость",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "Вставить STEP",
"cmd.file_save_mesh": "Сохранить как меш",
"cmd.file_slicers": "Установленные слайсеры",
"cmd.frame_member": "Элемент рамы",
"cmd.frame_profile_insert": "Вставить профиль",
"cmd.frame_profile_list": "Библиотека профилей",
"cmd.frame_profile_properties": "Свойства профиля",
"cmd.inspect_accessibility": "Анализ доступности",
"cmd.inspect_center_of_mass": "Центр масс",
"cmd.inspect_clearance": "Зазор",
@@ -568,6 +577,7 @@
"cmd.material_create": "Новый физический материал",
"cmd.material_delete": "Удалить физический материал",
"cmd.material_edit": "Изменить физический материал",
"cmd.material_inspect": "Проверить физический материал",
"cmd.material_list": "Список материалов",
"cmd.material_properties_directional": "Направленный модуль упругости",
"cmd.material_properties_validate": "Проверить данные физического материала",
@@ -1120,7 +1130,106 @@
"trace.stopped_by_contact": "Заканчивается на отсчёте {sample}: {a} прошёл бы сквозь {b}",
"trace.stopped_by_loop": "Заканчивается на отсчёте {sample}: контур через {joint} дальше не замыкается",
"ui.1_degree_of_freedom": "1 степень свободы",
"ui.3d_mouse_axes_reassigned": "Оси переназначены в настройках, и это действует и поверх этой калибровки.",
"ui.3d_mouse_button": "Кнопка {number}",
"ui.3d_mouse_button_nothing": "Ничего",
"ui.3d_mouse_button_settings": "Настройки 3D-мыши",
"ui.3d_mouse_buttons": "Кнопки",
"ui.3d_mouse_buttons_hint": "Нажмите кнопку на 3D-мыши, чтобы найти её здесь, и выберите, что она делает.",
"ui.3d_mouse_buttons_no_device": "Подключите 3D-мышь, чтобы назначить её кнопки.",
"ui.3d_mouse_buttons_reset": "Вернуть кнопки по умолчанию",
"ui.3d_mouse_calibrate": "Калибровать…",
"ui.3d_mouse_calibrate_tip": "Двигайте колпачок так, как просит окно, чтобы SolidCraft узнал, куда и насколько далеко движется каждая ось этой 3D-мыши. Сохраняется для этой 3D-мыши.",
"ui.3d_mouse_calibrate_title": "Калибровка 3D-мыши",
"ui.3d_mouse_calibrate_wait": "Один раз подвигайте 3D-мышь, чтобы SolidCraft её увидел.",
"ui.3d_mouse_calibrated": "Откалибрована",
"ui.3d_mouse_calibration": "Калибровка 3D-мыши",
"ui.3d_mouse_calibration_done": "Готово. Сохраните её, чтобы она применялась всякий раз, когда подключена эта 3D-мышь.",
"ui.3d_mouse_calibration_no_response": "Одна ось почти не сдвинулась, поэтому её нельзя откалибровать. Начните заново и каждый раз двигайте колпачок дальше.",
"ui.3d_mouse_circles_help": "Круги показывают, что устройство отправляет после калибровки. Тестовая модель показывает, что вид делает с этим после инверсии, скорости и назначения осей.",
"ui.3d_mouse_dead_zone": "Мёртвая зона",
"ui.3d_mouse_dead_zone_tip": "Насколько нужно сдвинуть колпачок, прежде чем сдвинется вид, в доле от полного хода. Увеличьте, если вид смещается, когда колпачок в покое.",
"ui.3d_mouse_denied": "SolidCraft видит 3D-мышь, но не имеет права её читать. Правило udev описано в заметках по настройке 3D-мыши.",
"ui.3d_mouse_denied_short": "Найдена, но недоступна для чтения",
"ui.3d_mouse_driven_by": "Какое откалиброванное движение устройства управляет этой осью. Если выбрать движение, которое уже использует другая ось, они поменяются местами.",
"ui.3d_mouse_driven_by_label": "управляется",
"ui.3d_mouse_driver": "Драйвер 3D-мыши",
"ui.3d_mouse_driver_auto": "Автоматически",
"ui.3d_mouse_driver_direct": "Напрямую (hidraw)",
"ui.3d_mouse_driver_fallback": "Пока чтение идёт через spacenavd: само устройство открыть не удалось.",
"ui.3d_mouse_driver_only_direct": "Чтение напрямую с устройства; spacenavd не запущен",
"ui.3d_mouse_driver_only_spacenavd": "Чтение через spacenavd, других вариантов нет",
"ui.3d_mouse_forget": "Забыть калибровку",
"ui.3d_mouse_ghost_left_viewport": "Область просмотра слева",
"ui.3d_mouse_ghost_left_window": "Окно слева",
"ui.3d_mouse_ghost_position": "Положение",
"ui.3d_mouse_ghost_right_viewport": "Область просмотра справа",
"ui.3d_mouse_hint": "Совместное перемещение и вращение вида; в эскизе — только панорама и масштаб.",
"ui.3d_mouse_kind_cap": "Возврат в центр",
"ui.3d_mouse_kind_question": "Какая у вас 3D-мышь?",
"ui.3d_mouse_kind_trackball": "Трекбол",
"ui.3d_mouse_looks_like_cap": "Значение держится, пока вы удерживаете, — как у устройства типа «Возврат в центр».",
"ui.3d_mouse_looks_like_trackball": "Значение возвращается, как только вы останавливаетесь, — как у трекбола.",
"ui.3d_mouse_motion_rotation_x": "Вращение X",
"ui.3d_mouse_motion_rotation_y": "Вращение Y",
"ui.3d_mouse_motion_rotation_z": "Вращение Z",
"ui.3d_mouse_motion_translation_x": "Смещение X",
"ui.3d_mouse_motion_translation_y": "Смещение Y",
"ui.3d_mouse_motion_translation_z": "Смещение Z",
"ui.3d_mouse_navigate": "Навигация с помощью 3D-мыши",
"ui.3d_mouse_none": "3D-мышь не найдена",
"ui.3d_mouse_not_calibrated": "Не откалибрована",
"ui.3d_mouse_offer": "Калибровка 3D-мыши исправляет перепутанные или перевёрнутые оси и настраивает скорость и ощущения.",
"ui.3d_mouse_offer_never": "Больше не показывать",
"ui.3d_mouse_offer_swap": "Переключить на Hidraw (прямой драйвер)",
"ui.3d_mouse_reading": "Показания",
"ui.3d_mouse_reset_axes": "Сбросить назначение осей",
"ui.3d_mouse_reset_view": "Сбросить вид",
"ui.3d_mouse_restore": "Восстановить настройки 3D-мыши",
"ui.3d_mouse_restore_ask": "Вернуть настройки 3D-мыши к значениям по умолчанию? Калибровка и назначение кнопок останутся.",
"ui.3d_mouse_section": "3D-мышь",
"ui.3d_mouse_show_in_view": "Показывать 3D-мышь в 3D-виде",
"ui.3d_mouse_show_in_view_tip": "Изображение вашей 3D-мыши в левом нижнем углу, которое двигается вместе с ней.",
"ui.3d_mouse_skip": "Моё устройство так не умеет",
"ui.3d_mouse_skip_tip": "Пропускает это движение в обе стороны; калибровка его не учитывает.",
"ui.3d_mouse_speed": "Скорость 3D-мыши",
"ui.3d_mouse_step_lift": "Поднимите колпачок вверх, удержите и отпустите.",
"ui.3d_mouse_step_press": "Нажмите на колпачок вниз, удержите и отпустите.",
"ui.3d_mouse_step_pull": "Потяните колпачок на себя, удержите и отпустите.",
"ui.3d_mouse_step_push": "Толкните колпачок от себя, удержите и отпустите.",
"ui.3d_mouse_step_rest": "Уберите руку с колпачка и не трогайте его.",
"ui.3d_mouse_step_rest_trackball": "Уберите руку с шара и не трогайте его.",
"ui.3d_mouse_step_ring_clockwise": "Поверните кольцо прокрутки на несколько щелчков по часовой стрелке.",
"ui.3d_mouse_step_ring_counterclockwise": "Поверните кольцо прокрутки на несколько щелчков против часовой стрелки.",
"ui.3d_mouse_step_roll_away": "Прокатите шар от себя несколько раз и дайте ему остановиться.",
"ui.3d_mouse_step_roll_left": "Прокатите шар влево несколько раз и дайте ему остановиться.",
"ui.3d_mouse_step_roll_right": "Прокатите шар вправо несколько раз и дайте ему остановиться.",
"ui.3d_mouse_step_roll_toward": "Прокатите шар к себе несколько раз и дайте ему остановиться.",
"ui.3d_mouse_step_slide_left": "Сдвиньте колпачок влево, удержите и отпустите.",
"ui.3d_mouse_step_slide_right": "Сдвиньте колпачок вправо, удержите и отпустите.",
"ui.3d_mouse_step_spin_clockwise": "Покрутите шар по часовой стрелке несколько раз и дайте ему остановиться.",
"ui.3d_mouse_step_spin_counterclockwise": "Покрутите шар против часовой стрелки несколько раз и дайте ему остановиться.",
"ui.3d_mouse_step_tilt_away": "Наклоните верх колпачка от себя (ближний край поднимается), удержите и отпустите.",
"ui.3d_mouse_step_tilt_left": "Наклоните колпачок влево (левый край вниз), удержите и отпустите.",
"ui.3d_mouse_step_tilt_right": "Наклоните колпачок вправо (правый край вниз), удержите и отпустите.",
"ui.3d_mouse_step_tilt_toward": "Наклоните верх колпачка к себе (ближний край опускается), удержите и отпустите.",
"ui.3d_mouse_step_touch": "Положите руку на колпачок, не нажимая на него.",
"ui.3d_mouse_step_twist_clockwise": "Поверните колпачок по часовой стрелке, удержите и отпустите.",
"ui.3d_mouse_step_twist_counterclockwise": "Поверните колпачок против часовой стрелки, удержите и отпустите.",
"ui.3d_mouse_switch_cap": "Калибровать как возврат в центр",
"ui.3d_mouse_switch_trackball": "Калибровать как трекбол",
"ui.3d_mouse_view_axis": "Ось вида",
"ui.3d_mouse_view_off": "Выкл.",
"ui.3d_mouse_view_off_tip": "Профиль драйвера 3D-мыши отключает эту ось, поэтому её движение не двигает вид. Если выбрать здесь движение другой оси, они поменяются местами, и это движение перестанет двигать вид.",
"ui.3d_mouse_view_orbit_sideways": "Орбита влево / вправо",
"ui.3d_mouse_view_orbit_up_down": "Орбита вверх / вниз",
"ui.3d_mouse_view_pan_sideways": "Сдвиг влево / вправо",
"ui.3d_mouse_view_pan_up_down": "Сдвиг вверх / вниз",
"ui.3d_mouse_view_roll": "Крен",
"ui.3d_mouse_view_unmapped": "Ось {n}",
"ui.3d_mouse_view_zoom": "Масштаб",
"ui.3d_mouse_waiting": "Ожидание движения",
"ui.3d_mouse_wiggle": "Если 3D-мышь подключена, слегка подвигайте её.",
"ui.3d_sketch": "3D-эскиз",
"ui.activate": "Активировать",
"ui.active_component": "Активный компонент",
@@ -1350,6 +1459,7 @@
"ui.blocked": "Заблокировано",
"ui.bodies": "Тела",
"ui.bodies_all_if_none": "Тела (все, если не выбраны)",
"ui.bottom": "Снизу",
"ui.box": "Бокс",
"ui.break": "Разорвать",
"ui.browser_bodies_one": "{count} тело",
@@ -1502,6 +1612,7 @@
"ui.grid_follows_view_hint": "На боковых видах сетка показывается на вертикальной плоскости, обращённой к вам, а не на земле",
"ui.ground_shadow": "Тень на земле",
"ui.groupSelected": "Группировать выбранное",
"ui.hatch": "Штриховка",
"ui.heading_add_ins": "НАДСТРОЙКИ",
"ui.heading_assemble": "СБОРКА",
"ui.heading_box": "БОКС",
@@ -1792,59 +1903,6 @@
"ui.smooth_wheel_zoom": "Плавное масштабирование колесом",
"ui.smooth_wheel_zoom_tip": "Отключите, чтобы каждое движение колеса применялось сразу, без сглаживания",
"ui.snap": "Привязка",
"ui.space_mouse_axes_reassigned": "Оси переназначены в настройках, и это действует и поверх этой калибровки.",
"ui.space_mouse_calibrate": "Калибровать…",
"ui.space_mouse_calibrate_tip": "Двигайте колпачок так, как просит окно, чтобы SolidCraft узнал, куда и насколько далеко движется каждая ось этой 3D-мыши. Сохраняется для этой 3D-мыши.",
"ui.space_mouse_calibrate_title": "Калибровка 3D-мыши",
"ui.space_mouse_calibrated": "Откалибрована",
"ui.space_mouse_calibration": "Калибровка 3D-мыши",
"ui.space_mouse_calibration_done": "Готово. Сохраните её, чтобы она применялась всякий раз, когда подключена эта 3D-мышь.",
"ui.space_mouse_calibration_no_response": "Одна ось почти не сдвинулась, поэтому её нельзя откалибровать. Начните заново и каждый раз двигайте колпачок дальше.",
"ui.space_mouse_dead_zone": "Мёртвая зона",
"ui.space_mouse_dead_zone_tip": "Насколько нужно сдвинуть колпачок, прежде чем сдвинется вид, в доле от полного хода. Увеличьте, если вид смещается, когда колпачок в покое.",
"ui.space_mouse_driven_by": "Какое откалиброванное движение устройства управляет этой осью. Если выбрать движение, которое уже использует другая ось, они поменяются местами.",
"ui.space_mouse_driven_by_label": "управляется",
"ui.space_mouse_driver": "Драйвер 3D-мыши",
"ui.space_mouse_driver_auto": "Автоматически",
"ui.space_mouse_driver_direct": "Напрямую (hidraw)",
"ui.space_mouse_driver_only_direct": "Чтение напрямую с устройства; spacenavd не запущен",
"ui.space_mouse_driver_only_spacenavd": "Чтение через spacenavd, других вариантов нет",
"ui.space_mouse_forget": "Забыть калибровку",
"ui.space_mouse_hint": "Совместное перемещение и вращение вида; в эскизе — только панорама и масштаб.",
"ui.space_mouse_motion_rotation_x": "Вращение X",
"ui.space_mouse_motion_rotation_y": "Вращение Y",
"ui.space_mouse_motion_rotation_z": "Вращение Z",
"ui.space_mouse_motion_translation_x": "Смещение X",
"ui.space_mouse_motion_translation_y": "Смещение Y",
"ui.space_mouse_motion_translation_z": "Смещение Z",
"ui.space_mouse_navigate": "Навигация через SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "3D-мышь не найдена",
"ui.space_mouse_not_calibrated": "Не откалибрована",
"ui.space_mouse_reading": "Показания",
"ui.space_mouse_reset_axes": "Сбросить назначение осей",
"ui.space_mouse_step_lift": "Поднимите колпачок вверх, удержите и отпустите.",
"ui.space_mouse_step_press": "Нажмите на колпачок вниз, удержите и отпустите.",
"ui.space_mouse_step_pull": "Потяните колпачок на себя, удержите и отпустите.",
"ui.space_mouse_step_push": "Толкните колпачок от себя, удержите и отпустите.",
"ui.space_mouse_step_rest": "Уберите руку с колпачка и не трогайте его.",
"ui.space_mouse_step_slide_left": "Сдвиньте колпачок влево, удержите и отпустите.",
"ui.space_mouse_step_slide_right": "Сдвиньте колпачок вправо, удержите и отпустите.",
"ui.space_mouse_step_tilt_back": "Наклоните колпачок назад (передний край вверх), удержите и отпустите.",
"ui.space_mouse_step_tilt_forward": "Наклоните колпачок вперёд (передний край вниз), удержите и отпустите.",
"ui.space_mouse_step_tilt_left": "Наклоните колпачок влево (левый край вниз), удержите и отпустите.",
"ui.space_mouse_step_tilt_right": "Наклоните колпачок вправо (правый край вниз), удержите и отпустите.",
"ui.space_mouse_step_twist_clockwise": "Поверните колпачок по часовой стрелке, удержите и отпустите.",
"ui.space_mouse_step_twist_counterclockwise": "Поверните колпачок против часовой стрелки, удержите и отпустите.",
"ui.space_mouse_view_axis": "Ось вида",
"ui.space_mouse_view_off": "Выкл.",
"ui.space_mouse_view_off_tip": "Профиль драйвера 3D-мыши отключает эту ось, поэтому её движение не двигает вид. Если выбрать здесь движение другой оси, они поменяются местами, и это движение перестанет двигать вид.",
"ui.space_mouse_view_orbit_sideways": "Орбита влево / вправо",
"ui.space_mouse_view_orbit_up_down": "Орбита вверх / вниз",
"ui.space_mouse_view_pan_sideways": "Сдвиг влево / вправо",
"ui.space_mouse_view_pan_up_down": "Сдвиг вверх / вниз",
"ui.space_mouse_view_roll": "Крен",
"ui.space_mouse_view_unmapped": "Ось {n}",
"ui.space_mouse_view_zoom": "Масштаб",
"ui.spacing": "Интервал",
"ui.spacing_2": "Интервал 2",
"ui.sphere": "Сфера",
+54 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Välj…",
"Circular": "Cirkulär",
"Circumference": "Omkrets",
"Clockwise": "Medurs",
"Close draft": "Stäng utkast",
"Collisions": "Kollisioner",
"Colour": "Färg",
@@ -321,6 +322,7 @@
"Search commands": "Sök kommandon",
"Search this library": "Sök i detta bibliotek",
"Section": "Sektion",
"Section position": "Profilens läge",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Markera annan: håll vänsterknappen stilla på modellen (eller högerklicka › Markera annan…) för att lista allt under markören; klicka igen på samma plats för att markera nästa objekt där. Dubbelklicka med mittenknappen för att gå till Hem (Inställningar › Navigering).",
"Select bodies (a face picks its body)": "Markera kroppar (en yta väljer sin kropp)",
"Select bodies or faces instead": "Markera kroppar eller ytor istället",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft stängdes innan dessa konstruktioner sparades. Deras senaste autospar kan öppnas.",
"Specialise": "Specialisera",
"Speed": "Hastighet",
"Square": "Kvadrat",
"Stand it up": "Res upp den",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Res upp den: ett kvartsvarv runt X, som en enda Flytta-funktion som du kan ångra.",
"Stand this part up?": "Resa upp denna del?",
"Start angle": "Startvinkel",
"Start bulge": "Startbukt",
"Start continuity": "Startkontinuitet",
"Step": "Steg",
@@ -411,6 +415,7 @@
"angle, height": "vinkel, höjd",
"appearance.dielectric_ior": "Dielektriskt IOR",
"appearance.metallic": "Metallisk",
"appearance.only_available": "Endast tillgängliga utseenden",
"appearance.pbr_help": "Visa den här ytan i Rendering eller en produkt-PNG. Ingen refraktion eller transmission.",
"appearance.pbr_opaque": "Ogenomskinlig PBR",
"appearance.roughness": "Skrovlighet",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "Infoga STEP",
"cmd.file_save_mesh": "Spara som mesh",
"cmd.file_slicers": "Installerade slicers",
"cmd.frame_member": "Ramdel",
"cmd.frame_profile_insert": "Infoga profil",
"cmd.frame_profile_list": "Profilbibliotek",
"cmd.frame_profile_properties": "Profilegenskaper",
"cmd.inspect_accessibility": "Tillgänglighetsanalys",
"cmd.inspect_center_of_mass": "Masscentrum",
"cmd.inspect_clearance": "Frigång",
@@ -568,6 +577,7 @@
"cmd.material_create": "Nytt fysiskt material",
"cmd.material_delete": "Ta bort fysiskt material",
"cmd.material_edit": "Redigera fysiskt material",
"cmd.material_inspect": "Granska fysiskt material",
"cmd.material_list": "Lista material",
"cmd.material_properties_directional": "Riktningsberoende elasticitetsmodul",
"cmd.material_properties_validate": "Validera fysiska materialdata",
@@ -1120,7 +1130,50 @@
"trace.stopped_by_contact": "Slutar vid prov {sample}: {a} skulle gå igenom {b}",
"trace.stopped_by_loop": "Slutar vid prov {sample}: slingan genom {joint} kan inte stängas längre",
"ui.1_degree_of_freedom": "1 frihetsgrad",
"ui.3d_mouse_calibrate": "Kalibrera…",
"ui.3d_mouse_calibrate_tip": "Flytta knappen som fönstret ber om, så att SolidCraft lär sig åt vilket håll och hur långt varje axel på denna 3D-mus går. Sparas för denna 3D-mus.",
"ui.3d_mouse_calibrate_title": "Kalibrera 3D-mus",
"ui.3d_mouse_calibrate_wait": "Rör 3D-musen en gång så att SolidCraft ser den.",
"ui.3d_mouse_calibrated": "Kalibrerad",
"ui.3d_mouse_calibration": "Kalibrering av 3D-mus",
"ui.3d_mouse_calibration_done": "Klart. Spara den för att använda den när denna 3D-mus är ansluten.",
"ui.3d_mouse_calibration_no_response": "En axel rörde sig knappt, så den kan inte kalibreras. Börja om och flytta knappen längre varje gång.",
"ui.3d_mouse_circles_help": "Cirklarna visar vad enheten skickar efter kalibrering. Testmodellen visar vad vyn gör med det, efter inversion, hastighet och axeltilldelning.",
"ui.3d_mouse_dead_zone": "Dödzon",
"ui.3d_mouse_dead_zone_tip": "Hur långt knappen måste röra sig innan vyn gör det, som andel av dess fulla rörelse. Höj den om vyn driver medan knappen vilar.",
"ui.3d_mouse_denied": "SolidCraft ser en 3D-mus men får inte läsa den. Se installationsanteckningarna för 3D-musen om udev-regeln.",
"ui.3d_mouse_denied_short": "Hittad, men går inte att läsa",
"ui.3d_mouse_driver_fallback": "Läser tills vidare via spacenavd: själva enheten kunde inte öppnas.",
"ui.3d_mouse_forget": "Glöm kalibrering",
"ui.3d_mouse_ghost_left_viewport": "Vyn vänster",
"ui.3d_mouse_ghost_left_window": "Fönster vänster",
"ui.3d_mouse_ghost_position": "Placering",
"ui.3d_mouse_ghost_right_viewport": "Vyn höger",
"ui.3d_mouse_hint": "Flytta och rotera vyn tillsammans; skisser använder endast panorering och zoom.",
"ui.3d_mouse_navigate": "Navigera med 3D-musen",
"ui.3d_mouse_none": "Ingen 3D-mus hittades",
"ui.3d_mouse_not_calibrated": "Inte kalibrerad",
"ui.3d_mouse_offer": "Att kalibrera din 3D-mus rättar förväxlade eller omvända axlar och ställer in hastighet och känsla.",
"ui.3d_mouse_offer_never": "Visa inte igen",
"ui.3d_mouse_offer_swap": "Byt till Hidraw (direkt drivrutin)",
"ui.3d_mouse_reading": "Avläsning",
"ui.3d_mouse_reset_view": "Återställ vyn",
"ui.3d_mouse_restore": "Återställ 3D-musens standardvärden",
"ui.3d_mouse_restore_ask": "Vill du återställa 3D-musens inställningar till standard? Din kalibrering och knapptilldelning behålls.",
"ui.3d_mouse_speed": "3D-musens hastighet",
"ui.3d_mouse_step_lift": "Lyft knappen uppåt, håll den och släpp sedan.",
"ui.3d_mouse_step_press": "Tryck ned knappen, håll den och släpp sedan.",
"ui.3d_mouse_step_pull": "Dra knappen mot dig, håll den och släpp sedan.",
"ui.3d_mouse_step_push": "Skjut knappen bort från dig, håll den och släpp sedan.",
"ui.3d_mouse_step_rest": "Ta bort handen från knappen och låt den vara stilla.",
"ui.3d_mouse_step_slide_left": "Skjut knappen åt vänster, håll den och släpp sedan.",
"ui.3d_mouse_step_slide_right": "Skjut knappen åt höger, håll den och släpp sedan.",
"ui.3d_mouse_step_tilt_left": "Luta knappen åt vänster (vänsterkanten nedåt), håll den och släpp sedan.",
"ui.3d_mouse_step_tilt_right": "Luta knappen åt höger (högerkanten nedåt), håll den och släpp sedan.",
"ui.3d_mouse_step_twist_clockwise": "Vrid knappen medurs, håll den och släpp sedan.",
"ui.3d_mouse_step_twist_counterclockwise": "Vrid knappen moturs, håll den och släpp sedan.",
"ui.3d_mouse_waiting": "Väntar på rörelse",
"ui.3d_mouse_wiggle": "Om du har en ansluten, rör lite på den.",
"ui.3d_sketch": "3D-skiss",
"ui.activate": "Aktivera",
"ui.active_component": "Aktiv komponent",
@@ -1502,6 +1555,7 @@
"ui.grid_follows_view_hint": "Sidovyer visar rutnätet på det upprätta planet som vetter mot dem istället för marken",
"ui.ground_shadow": "Markskugga",
"ui.groupSelected": "Gruppera markerade",
"ui.hatch": "Skraffering",
"ui.heading_add_ins": "TILLÄGG",
"ui.heading_assemble": "MONTERA",
"ui.heading_box": "LÅDA",
@@ -1792,34 +1846,6 @@
"ui.smooth_wheel_zoom": "Jämn hjulzoom",
"ui.smooth_wheel_zoom_tip": "Stäng av för att tillämpa varje hjulhändelse direkt, utan utjämningsfördröjning",
"ui.snap": "Snappa",
"ui.space_mouse_calibrate": "Kalibrera…",
"ui.space_mouse_calibrate_tip": "Flytta knappen som fönstret ber om, så att SolidCraft lär sig åt vilket håll och hur långt varje axel på denna 3D-mus går. Sparas för denna 3D-mus.",
"ui.space_mouse_calibrate_title": "Kalibrera 3D-mus",
"ui.space_mouse_calibrated": "Kalibrerad",
"ui.space_mouse_calibration": "Kalibrering av 3D-mus",
"ui.space_mouse_calibration_done": "Klart. Spara den för att använda den när denna 3D-mus är ansluten.",
"ui.space_mouse_calibration_no_response": "En axel rörde sig knappt, så den kan inte kalibreras. Börja om och flytta knappen längre varje gång.",
"ui.space_mouse_dead_zone": "Dödzon",
"ui.space_mouse_dead_zone_tip": "Hur långt knappen måste röra sig innan vyn gör det, som andel av dess fulla rörelse. Höj den om vyn driver medan knappen vilar.",
"ui.space_mouse_forget": "Glöm kalibrering",
"ui.space_mouse_hint": "Flytta och rotera vyn tillsammans; skisser använder endast panorering och zoom.",
"ui.space_mouse_navigate": "Navigera med SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Ingen 3D-mus hittades",
"ui.space_mouse_not_calibrated": "Inte kalibrerad",
"ui.space_mouse_reading": "Avläsning",
"ui.space_mouse_step_lift": "Lyft knappen uppåt, håll den och släpp sedan.",
"ui.space_mouse_step_press": "Tryck ned knappen, håll den och släpp sedan.",
"ui.space_mouse_step_pull": "Dra knappen mot dig, håll den och släpp sedan.",
"ui.space_mouse_step_push": "Skjut knappen bort från dig, håll den och släpp sedan.",
"ui.space_mouse_step_rest": "Ta bort handen från knappen och låt den vara stilla.",
"ui.space_mouse_step_slide_left": "Skjut knappen åt vänster, håll den och släpp sedan.",
"ui.space_mouse_step_slide_right": "Skjut knappen åt höger, håll den och släpp sedan.",
"ui.space_mouse_step_tilt_back": "Luta knappen bakåt (framkanten uppåt), håll den och släpp sedan.",
"ui.space_mouse_step_tilt_forward": "Luta knappen framåt (framkanten nedåt), håll den och släpp sedan.",
"ui.space_mouse_step_tilt_left": "Luta knappen åt vänster (vänsterkanten nedåt), håll den och släpp sedan.",
"ui.space_mouse_step_tilt_right": "Luta knappen åt höger (högerkanten nedåt), håll den och släpp sedan.",
"ui.space_mouse_step_twist_clockwise": "Vrid knappen medurs, håll den och släpp sedan.",
"ui.space_mouse_step_twist_counterclockwise": "Vrid knappen moturs, håll den och släpp sedan.",
"ui.spacing": "Avstånd",
"ui.spacing_2": "Avstånd 2",
"ui.sphere": "Sfär",
+111 -53
View File
@@ -72,6 +72,7 @@
"Choose…": "Seç…",
"Circular": "Dairesel",
"Circumference": "Çevre",
"Clockwise": "Saat yönünde",
"Close draft": "Taslağı kapat",
"Collisions": "Çarpışmalar",
"Colour": "Renk",
@@ -321,6 +322,7 @@
"Search commands": "Komutlarda ara",
"Search this library": "Bu kitaplıkta ara",
"Section": "Kesit",
"Section position": "Kesit konumu",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Diğerini seç: İmlecin altındaki her şeyi listelemek için sol tuşu model üzerinde hareket ettirmeden basılı tutun (veya sağ tık › Diğerini seç…); oradaki bir sonraki öğeyi seçmek için aynı noktaya tekrar tıklayın. Başlangıç görünümüne dönmek için orta tuşla çift tıklayın (Ayarlar › Gezinme).",
"Select bodies (a face picks its body)": "Gövdeleri seçin (bir yüz kendi gövdesini seçer)",
"Select bodies or faces instead": "Bunun yerine gövde veya yüz seçin",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft bu tasarımlar kaydedilmeden önce kapandı. Son otomatik kayıtları açılabilir.",
"Specialise": "Özelleştir",
"Speed": "Hız",
"Square": "Kare",
"Stand it up": "Dik çevir",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Dik çevir: X etrafında çeyrek tur; geri alınabilen tek bir Taşı özelliği olarak eklenir.",
"Stand this part up?": "Bu parça dik çevrilsin mi?",
"Start angle": "Başlangıç açısı",
"Start bulge": "Başlangıç şişkinliği",
"Start continuity": "Başlangıç sürekliliği",
"Step": "Adım",
@@ -411,6 +415,7 @@
"angle, height": "açı, yükseklik",
"appearance.dielectric_ior": "Dielektrik kırılma indisi",
"appearance.metallic": "Metaliklik",
"appearance.only_available": "Yalnızca kullanılabilir görünümler",
"appearance.pbr_help": "Bu yüzeyi Render'da veya bir ürün PNG'sinde görüntüleyin. Kırılma veya geçirgenlik yok.",
"appearance.pbr_opaque": "Opak PBR",
"appearance.roughness": "Pürüzlülük",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "STEP ekle",
"cmd.file_save_mesh": "Ağ olarak kaydet",
"cmd.file_slicers": "Yüklü dilimleyiciler",
"cmd.frame_member": "Çerçeve Elemanı",
"cmd.frame_profile_insert": "Profil Ekle",
"cmd.frame_profile_list": "Profil Kitaplığı",
"cmd.frame_profile_properties": "Profil Özellikleri",
"cmd.inspect_accessibility": "Erişilebilirlik analizi",
"cmd.inspect_center_of_mass": "Kütle merkezi",
"cmd.inspect_clearance": "Açıklık",
@@ -568,6 +577,7 @@
"cmd.material_create": "Yeni fiziksel malzeme",
"cmd.material_delete": "Fiziksel malzemeyi sil",
"cmd.material_edit": "Fiziksel malzemeyi düzenle",
"cmd.material_inspect": "Fiziksel malzemeyi incele",
"cmd.material_list": "Malzemeleri listele",
"cmd.material_properties_directional": "Yöne bağlı elastisite modülü",
"cmd.material_properties_validate": "Fiziksel malzeme verilerini kontrol et",
@@ -1120,7 +1130,106 @@
"trace.stopped_by_contact": "Örnek {sample} noktasında biter: {a}, {b} içinden geçecek",
"trace.stopped_by_loop": "Örnek {sample} noktasında biter: {joint} üzerinden geçen döngü daha fazla kapanamaz",
"ui.1_degree_of_freedom": "1 serbestlik derecesi",
"ui.3d_mouse_axes_reassigned": "Eksenler Tercihler'de yeniden atanmış; bu, bu kalibrasyonun üstüne de uygulanır.",
"ui.3d_mouse_button": "Düğme {number}",
"ui.3d_mouse_button_nothing": "Hiçbir şey",
"ui.3d_mouse_button_settings": "3B fare ayarları",
"ui.3d_mouse_buttons": "Düğmeler",
"ui.3d_mouse_buttons_hint": "Burada bulmak için 3B farenizdeki bir düğmeye basın, sonra ne yapacağını seçin.",
"ui.3d_mouse_buttons_no_device": "Düğmelerinin ne yapacağını seçmek için bir 3B fare bağlayın.",
"ui.3d_mouse_buttons_reset": "Varsayılan düğmelere dön",
"ui.3d_mouse_calibrate": "Kalibre et…",
"ui.3d_mouse_calibrate_tip": "Pencerenin istediği gibi başlığı hareket ettirin; SolidCraft bu 3B farenin her ekseninin hangi yöne ve ne kadar gittiğini öğrensin. Bu 3B fare için kaydedilir.",
"ui.3d_mouse_calibrate_title": "3B fareyi kalibre et",
"ui.3d_mouse_calibrate_wait": "SolidCraft'ın görebilmesi için 3D fareyi bir kez hareket ettirin.",
"ui.3d_mouse_calibrated": "Kalibre edildi",
"ui.3d_mouse_calibration": "3B fare kalibrasyonu",
"ui.3d_mouse_calibration_done": "Bitti. Bu 3B fare her bağlandığında kullanılması için kaydedin.",
"ui.3d_mouse_calibration_no_response": "Bir eksen neredeyse hiç hareket etmedi, bu yüzden kalibre edilemiyor. Baştan başlayın ve başlığı her seferinde daha fazla hareket ettirin.",
"ui.3d_mouse_circles_help": "Daireler, aygıtın kalibrasyondan sonra gönderdiğini gösterir. Test modeli, görünümün bunu ters çevirme, hız ve eksen atamasından sonra ne yaptığını gösterir.",
"ui.3d_mouse_dead_zone": "Ölü bölge",
"ui.3d_mouse_dead_zone_tip": "Görünüm hareket etmeden önce başlığın ne kadar hareket etmesi gerektiği, tam hareket aralığının oranı olarak. Başlık dururken görünüm kayıyorsa artırın.",
"ui.3d_mouse_denied": "SolidCraft bir 3D fare görüyor ama onu okumasına izin verilmiyor. udev kuralı için 3D fare kurulum notlarına bakın.",
"ui.3d_mouse_denied_short": "Bulundu, ancak okunamıyor",
"ui.3d_mouse_driven_by": "Bu ekseni cihazın hangi kalibre edilmiş hareketinin yönettiği. Başka bir eksenin zaten kullandığını seçerseniz ikisi yer değiştirir.",
"ui.3d_mouse_driven_by_label": "kumanda eden",
"ui.3d_mouse_driver": "3B fare sürücüsü",
"ui.3d_mouse_driver_auto": "Otomatik",
"ui.3d_mouse_driver_direct": "Doğrudan (hidraw)",
"ui.3d_mouse_driver_fallback": "Şimdilik spacenavd üzerinden okunuyor: aygıtın kendisi açılamadı.",
"ui.3d_mouse_driver_only_direct": "Aygıt doğrudan okunuyor; spacenavd çalışmıyor",
"ui.3d_mouse_driver_only_spacenavd": "spacenavd üzerinden okunuyor, tek seçenek bu",
"ui.3d_mouse_forget": "Kalibrasyonu unut",
"ui.3d_mouse_ghost_left_viewport": "Sol görünüm",
"ui.3d_mouse_ghost_left_window": "Sol pencere",
"ui.3d_mouse_ghost_position": "Konum",
"ui.3d_mouse_ghost_right_viewport": "Sağ görünüm",
"ui.3d_mouse_hint": "Görünümü aynı anda kaydırın ve döndürün; eskizlerde yalnızca kaydırma ve yakınlaştırma kullanılır.",
"ui.3d_mouse_kind_cap": "Merkeze dönüş",
"ui.3d_mouse_kind_question": "Ne tür bir 3B fare?",
"ui.3d_mouse_kind_trackball": "İztopu",
"ui.3d_mouse_looks_like_cap": "Tuttuğunuz sürece değer yerinde kalıyor, “Merkeze dönüş” türü bir aygıttaki gibi.",
"ui.3d_mouse_looks_like_trackball": "Hareketi bırakır bırakmaz değer geri dönüyor, iztopunda olduğu gibi.",
"ui.3d_mouse_motion_rotation_x": "Dönme X",
"ui.3d_mouse_motion_rotation_y": "Dönme Y",
"ui.3d_mouse_motion_rotation_z": "Dönme Z",
"ui.3d_mouse_motion_translation_x": "Öteleme X",
"ui.3d_mouse_motion_translation_y": "Öteleme Y",
"ui.3d_mouse_motion_translation_z": "Öteleme Z",
"ui.3d_mouse_navigate": "3D fare ile gezin",
"ui.3d_mouse_none": "3B fare bulunamadı",
"ui.3d_mouse_not_calibrated": "Kalibre edilmedi",
"ui.3d_mouse_offer": "3D farenizi kalibre etmek, yer değiştirmiş veya ters dönmüş eksenleri düzeltir; hızını ve hissini ayarlar.",
"ui.3d_mouse_offer_never": "Bir daha gösterme",
"ui.3d_mouse_offer_swap": "Hidraw'a geç (doğrudan sürücü)",
"ui.3d_mouse_reading": "Okunan değer",
"ui.3d_mouse_reset_axes": "Eksen atamasını sıfırla",
"ui.3d_mouse_reset_view": "Görünümü sıfırla",
"ui.3d_mouse_restore": "3D fare varsayılanlarını geri yükle",
"ui.3d_mouse_restore_ask": "3D fare ayarları varsayılanlara döndürülsün mü? Kalibrasyon ve düğme eşlemesi korunur.",
"ui.3d_mouse_section": "3D fare",
"ui.3d_mouse_show_in_view": "3B fareyi 3B görünümde göster",
"ui.3d_mouse_show_in_view_tip": "Sol alt köşede, siz hareket ettirdikçe hareket eden bir 3B fare resmi.",
"ui.3d_mouse_skip": "Cihazım bunu yapamıyor",
"ui.3d_mouse_skip_tip": "Bu hareketi iki yönde de atlar; kalibrasyon onu dışarıda bırakır.",
"ui.3d_mouse_speed": "3D fare hızı",
"ui.3d_mouse_step_lift": "Başlığı yukarı kaldırın, tutun, sonra bırakın.",
"ui.3d_mouse_step_press": "Başlığı aşağı bastırın, tutun, sonra bırakın.",
"ui.3d_mouse_step_pull": "Başlığı kendinize doğru çekin, tutun, sonra bırakın.",
"ui.3d_mouse_step_push": "Başlığı sizden uzağa itin, tutun, sonra bırakın.",
"ui.3d_mouse_step_rest": "Elinizi başlıktan çekin ve hareketsiz bırakın.",
"ui.3d_mouse_step_rest_trackball": "Elinizi toptan çekin ve hareketsiz bırakın.",
"ui.3d_mouse_step_ring_clockwise": "Kaydırma halkasını saat yönünde birkaç tık çevirin.",
"ui.3d_mouse_step_ring_counterclockwise": "Kaydırma halkasını saat yönünün tersine birkaç tık çevirin.",
"ui.3d_mouse_step_roll_away": "Topu birkaç kez sizden uzağa yuvarlayın, sonra durmasını bekleyin.",
"ui.3d_mouse_step_roll_left": "Topu birkaç kez sola yuvarlayın, sonra durmasını bekleyin.",
"ui.3d_mouse_step_roll_right": "Topu birkaç kez sağa yuvarlayın, sonra durmasını bekleyin.",
"ui.3d_mouse_step_roll_toward": "Topu birkaç kez kendinize doğru yuvarlayın, sonra durmasını bekleyin.",
"ui.3d_mouse_step_slide_left": "Başlığı sola kaydırın, tutun, sonra bırakın.",
"ui.3d_mouse_step_slide_right": "Başlığı sağa kaydırın, tutun, sonra bırakın.",
"ui.3d_mouse_step_spin_clockwise": "Topu birkaç kez saat yönünde döndürün, sonra durmasını bekleyin.",
"ui.3d_mouse_step_spin_counterclockwise": "Topu birkaç kez saat yönünün tersine döndürün, sonra durmasını bekleyin.",
"ui.3d_mouse_step_tilt_away": "Başlığın üstünü sizden uzağa eğin (size en yakın kenar yukarı kalkar), tutun, sonra bırakın.",
"ui.3d_mouse_step_tilt_left": "Başlığı sola eğin (sol kenar aşağı), tutun, sonra bırakın.",
"ui.3d_mouse_step_tilt_right": "Başlığı sağa eğin (sağ kenar aşağı), tutun, sonra bırakın.",
"ui.3d_mouse_step_tilt_toward": "Başlığın üstünü kendinize doğru eğin (size en yakın kenar aşağı iner), tutun, sonra bırakın.",
"ui.3d_mouse_step_touch": "Elinizi başlığa itmeden koyun.",
"ui.3d_mouse_step_twist_clockwise": "Başlığı saat yönünde çevirin, tutun, sonra bırakın.",
"ui.3d_mouse_step_twist_counterclockwise": "Başlığı saat yönünün tersine çevirin, tutun, sonra bırakın.",
"ui.3d_mouse_switch_cap": "Merkeze dönüş olarak kalibre et",
"ui.3d_mouse_switch_trackball": "İztopu olarak kalibre et",
"ui.3d_mouse_view_axis": "Görünüm ekseni",
"ui.3d_mouse_view_off": "Kapalı",
"ui.3d_mouse_view_off_tip": "3D fare sürücüsünün profili bu ekseni kapatıyor, bu yüzden hareketi görünümü oynatmaz. Burada başka bir eksenin hareketini seçerseniz ikisi yer değiştirir ve o hareket görünümü oynatmayı bırakır.",
"ui.3d_mouse_view_orbit_sideways": "Sola / sağa yörünge",
"ui.3d_mouse_view_orbit_up_down": "Yukarı / aşağı yörünge",
"ui.3d_mouse_view_pan_sideways": "Sola / sağa kaydır",
"ui.3d_mouse_view_pan_up_down": "Yukarı / aşağı kaydır",
"ui.3d_mouse_view_roll": "Yuvarlanma",
"ui.3d_mouse_view_unmapped": "Eksen {n}",
"ui.3d_mouse_view_zoom": "Yakınlaştır",
"ui.3d_mouse_waiting": "Hareket bekleniyor",
"ui.3d_mouse_wiggle": "Takılı bir tane varsa biraz oynatın.",
"ui.3d_sketch": "3D eskiz",
"ui.activate": "Etkinleştir",
"ui.active_component": "Etkin bileşen",
@@ -1350,6 +1459,7 @@
"ui.blocked": "Engellenmiş",
"ui.bodies": "Gövdeler",
"ui.bodies_all_if_none": "Gövdeler (yoksa tümü)",
"ui.bottom": "Alt",
"ui.box": "Kutu",
"ui.break": "Kır",
"ui.browser_bodies_one": "{count} gövde",
@@ -1502,6 +1612,7 @@
"ui.grid_follows_view_hint": "Yan görünümlerde ızgara, zemin yerine onlara bakan dik düzlemde gösterilir",
"ui.ground_shadow": "Zemin gölgesi",
"ui.groupSelected": "Seçimi grupla",
"ui.hatch": "Tarama",
"ui.heading_add_ins": "EKLENTİLER",
"ui.heading_assemble": "MONTAJ",
"ui.heading_box": "KUTU",
@@ -1792,59 +1903,6 @@
"ui.smooth_wheel_zoom": "Yumuşak tekerlek yakınlaştırması",
"ui.smooth_wheel_zoom_tip": "Her tekerlek hareketini yumuşatma olmadan hemen uygulamak için kapatın",
"ui.snap": "Yakala",
"ui.space_mouse_axes_reassigned": "Eksenler Tercihler'de yeniden atanmış; bu, bu kalibrasyonun üstüne de uygulanır.",
"ui.space_mouse_calibrate": "Kalibre et…",
"ui.space_mouse_calibrate_tip": "Pencerenin istediği gibi başlığı hareket ettirin; SolidCraft bu 3B farenin her ekseninin hangi yöne ve ne kadar gittiğini öğrensin. Bu 3B fare için kaydedilir.",
"ui.space_mouse_calibrate_title": "3B fareyi kalibre et",
"ui.space_mouse_calibrated": "Kalibre edildi",
"ui.space_mouse_calibration": "3B fare kalibrasyonu",
"ui.space_mouse_calibration_done": "Bitti. Bu 3B fare her bağlandığında kullanılması için kaydedin.",
"ui.space_mouse_calibration_no_response": "Bir eksen neredeyse hiç hareket etmedi, bu yüzden kalibre edilemiyor. Baştan başlayın ve başlığı her seferinde daha fazla hareket ettirin.",
"ui.space_mouse_dead_zone": "Ölü bölge",
"ui.space_mouse_dead_zone_tip": "Görünüm hareket etmeden önce başlığın ne kadar hareket etmesi gerektiği, tam hareket aralığının oranı olarak. Başlık dururken görünüm kayıyorsa artırın.",
"ui.space_mouse_driven_by": "Bu ekseni cihazın hangi kalibre edilmiş hareketinin yönettiği. Başka bir eksenin zaten kullandığını seçerseniz ikisi yer değiştirir.",
"ui.space_mouse_driven_by_label": "kumanda eden",
"ui.space_mouse_driver": "3B fare sürücüsü",
"ui.space_mouse_driver_auto": "Otomatik",
"ui.space_mouse_driver_direct": "Doğrudan (hidraw)",
"ui.space_mouse_driver_only_direct": "Aygıt doğrudan okunuyor; spacenavd çalışmıyor",
"ui.space_mouse_driver_only_spacenavd": "spacenavd üzerinden okunuyor, tek seçenek bu",
"ui.space_mouse_forget": "Kalibrasyonu unut",
"ui.space_mouse_hint": "Görünümü aynı anda kaydırın ve döndürün; eskizlerde yalnızca kaydırma ve yakınlaştırma kullanılır.",
"ui.space_mouse_motion_rotation_x": "Dönme X",
"ui.space_mouse_motion_rotation_y": "Dönme Y",
"ui.space_mouse_motion_rotation_z": "Dönme Z",
"ui.space_mouse_motion_translation_x": "Öteleme X",
"ui.space_mouse_motion_translation_y": "Öteleme Y",
"ui.space_mouse_motion_translation_z": "Öteleme Z",
"ui.space_mouse_navigate": "SpaceNavigator / SpaceMouse ile gezin",
"ui.space_mouse_none": "3B fare bulunamadı",
"ui.space_mouse_not_calibrated": "Kalibre edilmedi",
"ui.space_mouse_reading": "Okunan değer",
"ui.space_mouse_reset_axes": "Eksen atamasını sıfırla",
"ui.space_mouse_step_lift": "Başlığı yukarı kaldırın, tutun, sonra bırakın.",
"ui.space_mouse_step_press": "Başlığı aşağı bastırın, tutun, sonra bırakın.",
"ui.space_mouse_step_pull": "Başlığı kendinize doğru çekin, tutun, sonra bırakın.",
"ui.space_mouse_step_push": "Başlığı sizden uzağa itin, tutun, sonra bırakın.",
"ui.space_mouse_step_rest": "Elinizi başlıktan çekin ve hareketsiz bırakın.",
"ui.space_mouse_step_slide_left": "Başlığı sola kaydırın, tutun, sonra bırakın.",
"ui.space_mouse_step_slide_right": "Başlığı sağa kaydırın, tutun, sonra bırakın.",
"ui.space_mouse_step_tilt_back": "Başlığı geriye eğin (ön kenar yukarı), tutun, sonra bırakın.",
"ui.space_mouse_step_tilt_forward": "Başlığı öne eğin (ön kenar aşağı), tutun, sonra bırakın.",
"ui.space_mouse_step_tilt_left": "Başlığı sola eğin (sol kenar aşağı), tutun, sonra bırakın.",
"ui.space_mouse_step_tilt_right": "Başlığı sağa eğin (sağ kenar aşağı), tutun, sonra bırakın.",
"ui.space_mouse_step_twist_clockwise": "Başlığı saat yönünde çevirin, tutun, sonra bırakın.",
"ui.space_mouse_step_twist_counterclockwise": "Başlığı saat yönünün tersine çevirin, tutun, sonra bırakın.",
"ui.space_mouse_view_axis": "Görünüm ekseni",
"ui.space_mouse_view_off": "Kapalı",
"ui.space_mouse_view_off_tip": "3D fare sürücüsünün profili bu ekseni kapatıyor, bu yüzden hareketi görünümü oynatmaz. Burada başka bir eksenin hareketini seçerseniz ikisi yer değiştirir ve o hareket görünümü oynatmayı bırakır.",
"ui.space_mouse_view_orbit_sideways": "Sola / sağa yörünge",
"ui.space_mouse_view_orbit_up_down": "Yukarı / aşağı yörünge",
"ui.space_mouse_view_pan_sideways": "Sola / sağa kaydır",
"ui.space_mouse_view_pan_up_down": "Yukarı / aşağı kaydır",
"ui.space_mouse_view_roll": "Yuvarlanma",
"ui.space_mouse_view_unmapped": "Eksen {n}",
"ui.space_mouse_view_zoom": "Yakınlaştır",
"ui.spacing": "Aralık",
"ui.spacing_2": "Aralık 2",
"ui.sphere": "Küre",
+111 -53
View File
@@ -72,6 +72,7 @@
"Choose…": "Вибрати…",
"Circular": "Круговий",
"Circumference": "Довжина кола",
"Clockwise": "За годинниковою стрілкою",
"Close draft": "Закрити чернетку",
"Collisions": "Зіткнення",
"Colour": "Колір",
@@ -322,6 +323,7 @@
"Search commands": "Пошук команд",
"Search this library": "Пошук у цій бібліотеці",
"Section": "Переріз",
"Section position": "Положення перерізу",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Вибір іншого: утримуйте ліву кнопку нерухомо на моделі (або клацніть правою › Вибрати інше…), щоб побачити список усього під курсором; клацніть ще раз у тому ж місці, щоб вибрати наступний об'єкт там. Двічі клацніть середньою кнопкою, щоб перейти до головного виду (Налаштування › Навігація).",
"Select bodies (a face picks its body)": "Виділяти тіла (грань виділяє своє тіло)",
"Select bodies or faces instead": "Натомість виділіть тіла або грані",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft закрився до того, як ці проєкти було збережено. Можна відкрити їхнє останнє автозбереження.",
"Specialise": "Спеціалізувати",
"Speed": "Швидкість",
"Square": "Квадрат",
"Stand it up": "Вирівняти",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Вирівняти: поворот на чверть оберту навколо X як одна операція переміщення, яку можна скасувати.",
"Stand this part up?": "Вирівняти цю деталь?",
"Start angle": "Початковий кут",
"Start bulge": "Роздуття початку",
"Start continuity": "Неперервність початку",
"Step": "Крок",
@@ -412,6 +416,7 @@
"angle, height": "кут, висота",
"appearance.dielectric_ior": "Діелектричний IOR",
"appearance.metallic": "Металевість",
"appearance.only_available": "Лише доступні зовнішні вигляди",
"appearance.pbr_help": "Перегляньте цю поверхню в режимі рендерингу або як PNG-зображення виробу. Без заломлення чи пропускання.",
"appearance.pbr_opaque": "Непрозорий PBR",
"appearance.roughness": "Шорсткість",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Вставити STEP",
"cmd.file_save_mesh": "Зберегти як сітку",
"cmd.file_slicers": "Встановлені слайсери",
"cmd.frame_member": "Елемент рами",
"cmd.frame_profile_insert": "Вставити профіль",
"cmd.frame_profile_list": "Бібліотека профілів",
"cmd.frame_profile_properties": "Властивості профілю",
"cmd.inspect_accessibility": "Аналіз доступності",
"cmd.inspect_center_of_mass": "Центр мас",
"cmd.inspect_clearance": "Зазор",
@@ -569,6 +578,7 @@
"cmd.material_create": "Новий фізичний матеріал",
"cmd.material_delete": "Видалити фізичний матеріал",
"cmd.material_edit": "Редагувати фізичний матеріал",
"cmd.material_inspect": "Перевірити фізичний матеріал",
"cmd.material_list": "Список матеріалів",
"cmd.material_properties_directional": "Спрямований модуль пружності",
"cmd.material_properties_validate": "Перевірити дані фізичного матеріалу",
@@ -1121,7 +1131,106 @@
"trace.stopped_by_contact": "Закінчується на відліку {sample}: {a} пройшов би крізь {b}",
"trace.stopped_by_loop": "Закінчується на відліку {sample}: контур через {joint} далі не замикається",
"ui.1_degree_of_freedom": "1 ступінь вільності",
"ui.3d_mouse_axes_reassigned": "Осі перепризначено в налаштуваннях, і це діє й поверх цього калібрування.",
"ui.3d_mouse_button": "Кнопка {number}",
"ui.3d_mouse_button_nothing": "Нічого",
"ui.3d_mouse_button_settings": "Налаштування 3D-миші",
"ui.3d_mouse_buttons": "Кнопки",
"ui.3d_mouse_buttons_hint": "Натисніть кнопку на 3D-миші, щоб знайти її тут, і виберіть, що вона робить.",
"ui.3d_mouse_buttons_no_device": "Під'єднайте 3D-мишу, щоб призначити її кнопки.",
"ui.3d_mouse_buttons_reset": "Повернути типові кнопки",
"ui.3d_mouse_calibrate": "Калібрувати…",
"ui.3d_mouse_calibrate_tip": "Рухайте ковпачок так, як просить вікно, щоб SolidCraft дізнався, куди й наскільки далеко рухається кожна вісь цієї 3D-миші. Зберігається для цієї 3D-миші.",
"ui.3d_mouse_calibrate_title": "Калібрування 3D-миші",
"ui.3d_mouse_calibrate_wait": "Один раз порухайте 3D-мишу, щоб SolidCraft її побачив.",
"ui.3d_mouse_calibrated": "Відкалібровано",
"ui.3d_mouse_calibration": "Калібрування 3D-миші",
"ui.3d_mouse_calibration_done": "Готово. Збережіть його, щоб воно застосовувалося щоразу, коли підключено цю 3D-мишу.",
"ui.3d_mouse_calibration_no_response": "Одна вісь майже не зрушила, тому її не можна відкалібрувати. Почніть спочатку й щоразу рухайте ковпачок далі.",
"ui.3d_mouse_circles_help": "Кола показують, що пристрій надсилає після калібрування. Тестова модель показує, що вигляд робить із цим після інверсії, швидкості та призначення осей.",
"ui.3d_mouse_dead_zone": "Мертва зона",
"ui.3d_mouse_dead_zone_tip": "Наскільки треба зсунути ковпачок, перш ніж зрушить вигляд, у частці від повного ходу. Збільште, якщо вигляд зміщується, коли ковпачок у спокої.",
"ui.3d_mouse_denied": "SolidCraft бачить 3D-мишу, але не має права її читати. Правило udev описано в нотатках із налаштування 3D-миші.",
"ui.3d_mouse_denied_short": "Знайдено, але не читається",
"ui.3d_mouse_driven_by": "Який відкалібрований рух пристрою керує цією віссю. Якщо вибрати рух, який уже використовує інша вісь, вони поміняються місцями.",
"ui.3d_mouse_driven_by_label": "керується",
"ui.3d_mouse_driver": "Драйвер 3D-миші",
"ui.3d_mouse_driver_auto": "Автоматично",
"ui.3d_mouse_driver_direct": "Напряму (hidraw)",
"ui.3d_mouse_driver_fallback": "Наразі читання йде через spacenavd: сам пристрій відкрити не вдалося.",
"ui.3d_mouse_driver_only_direct": "Читання напряму з пристрою; spacenavd не запущено",
"ui.3d_mouse_driver_only_spacenavd": "Читання через spacenavd, інших варіантів немає",
"ui.3d_mouse_forget": "Забути калібрування",
"ui.3d_mouse_ghost_left_viewport": "Ліворуч у вікні перегляду",
"ui.3d_mouse_ghost_left_window": "Ліворуч у вікні",
"ui.3d_mouse_ghost_position": "Розташування",
"ui.3d_mouse_ghost_right_viewport": "Праворуч у вікні перегляду",
"ui.3d_mouse_hint": "Переміщуйте й обертайте вид разом; в ескізах доступні лише панорамування та масштаб.",
"ui.3d_mouse_kind_cap": "Повернення до центру",
"ui.3d_mouse_kind_question": "Яка у вас 3D-миша?",
"ui.3d_mouse_kind_trackball": "Трекбол",
"ui.3d_mouse_looks_like_cap": "Значення тримається, поки ви утримуєте, — як у пристрою типу «Повернення до центру».",
"ui.3d_mouse_looks_like_trackball": "Значення повертається, щойно ви зупиняєтеся, — як у трекбола.",
"ui.3d_mouse_motion_rotation_x": "Обертання X",
"ui.3d_mouse_motion_rotation_y": "Обертання Y",
"ui.3d_mouse_motion_rotation_z": "Обертання Z",
"ui.3d_mouse_motion_translation_x": "Зміщення X",
"ui.3d_mouse_motion_translation_y": "Зміщення Y",
"ui.3d_mouse_motion_translation_z": "Зміщення Z",
"ui.3d_mouse_navigate": "Навігація за допомогою 3D-миші",
"ui.3d_mouse_none": "3D-мишу не знайдено",
"ui.3d_mouse_not_calibrated": "Не відкалібровано",
"ui.3d_mouse_offer": "Калібрування 3D-миші виправляє переплутані або обернені осі та налаштовує швидкість і відчуття.",
"ui.3d_mouse_offer_never": "Більше не показувати",
"ui.3d_mouse_offer_swap": "Перемкнути на Hidraw (прямий драйвер)",
"ui.3d_mouse_reading": "Показники",
"ui.3d_mouse_reset_axes": "Скинути призначення осей",
"ui.3d_mouse_reset_view": "Скинути вигляд",
"ui.3d_mouse_restore": "Відновити налаштування 3D-миші",
"ui.3d_mouse_restore_ask": "Повернути налаштування 3D-миші до типових? Калібрування і призначення кнопок залишаться.",
"ui.3d_mouse_section": "3D-миша",
"ui.3d_mouse_show_in_view": "Показувати 3D-мишу в 3D-вигляді",
"ui.3d_mouse_show_in_view_tip": "Зображення вашої 3D-миші в лівому нижньому куті, що рухається разом із нею.",
"ui.3d_mouse_skip": "Мій пристрій так не вміє",
"ui.3d_mouse_skip_tip": "Пропускає цей рух в обидва боки; калібрування його не враховує.",
"ui.3d_mouse_speed": "Швидкість 3D-миші",
"ui.3d_mouse_step_lift": "Підніміть ковпачок угору, утримайте й відпустіть.",
"ui.3d_mouse_step_press": "Натисніть ковпачок униз, утримайте й відпустіть.",
"ui.3d_mouse_step_pull": "Потягніть ковпачок до себе, утримайте й відпустіть.",
"ui.3d_mouse_step_push": "Штовхніть ковпачок від себе, утримайте й відпустіть.",
"ui.3d_mouse_step_rest": "Приберіть руку з ковпачка й не торкайтеся його.",
"ui.3d_mouse_step_rest_trackball": "Приберіть руку з кулі й не торкайтеся її.",
"ui.3d_mouse_step_ring_clockwise": "Поверніть кільце прокручування на кілька клацань за годинниковою стрілкою.",
"ui.3d_mouse_step_ring_counterclockwise": "Поверніть кільце прокручування на кілька клацань проти годинникової стрілки.",
"ui.3d_mouse_step_roll_away": "Прокотіть кулю від себе кілька разів і дайте їй зупинитися.",
"ui.3d_mouse_step_roll_left": "Прокотіть кулю ліворуч кілька разів і дайте їй зупинитися.",
"ui.3d_mouse_step_roll_right": "Прокотіть кулю праворуч кілька разів і дайте їй зупинитися.",
"ui.3d_mouse_step_roll_toward": "Прокотіть кулю до себе кілька разів і дайте їй зупинитися.",
"ui.3d_mouse_step_slide_left": "Зсуньте ковпачок ліворуч, утримайте й відпустіть.",
"ui.3d_mouse_step_slide_right": "Зсуньте ковпачок праворуч, утримайте й відпустіть.",
"ui.3d_mouse_step_spin_clockwise": "Покрутіть кулю за годинниковою стрілкою кілька разів і дайте їй зупинитися.",
"ui.3d_mouse_step_spin_counterclockwise": "Покрутіть кулю проти годинникової стрілки кілька разів і дайте їй зупинитися.",
"ui.3d_mouse_step_tilt_away": "Нахиліть верх ковпачка від себе (ближній край піднімається), утримайте й відпустіть.",
"ui.3d_mouse_step_tilt_left": "Нахиліть ковпачок ліворуч (лівий край униз), утримайте й відпустіть.",
"ui.3d_mouse_step_tilt_right": "Нахиліть ковпачок праворуч (правий край униз), утримайте й відпустіть.",
"ui.3d_mouse_step_tilt_toward": "Нахиліть верх ковпачка до себе (ближній край опускається), утримайте й відпустіть.",
"ui.3d_mouse_step_touch": "Покладіть руку на ковпачок, не натискаючи на нього.",
"ui.3d_mouse_step_twist_clockwise": "Поверніть ковпачок за годинниковою стрілкою, утримайте й відпустіть.",
"ui.3d_mouse_step_twist_counterclockwise": "Поверніть ковпачок проти годинникової стрілки, утримайте й відпустіть.",
"ui.3d_mouse_switch_cap": "Калібрувати як повернення до центру",
"ui.3d_mouse_switch_trackball": "Калібрувати як трекбол",
"ui.3d_mouse_view_axis": "Вісь вигляду",
"ui.3d_mouse_view_off": "Вимк.",
"ui.3d_mouse_view_off_tip": "Профіль драйвера 3D-миші вимикає цю вісь, тож її рух не рухає вигляд. Якщо вибрати тут рух іншої осі, вони поміняються місцями, і цей рух перестане рухати вигляд.",
"ui.3d_mouse_view_orbit_sideways": "Орбіта ліворуч / праворуч",
"ui.3d_mouse_view_orbit_up_down": "Орбіта вгору / вниз",
"ui.3d_mouse_view_pan_sideways": "Зсув ліворуч / праворуч",
"ui.3d_mouse_view_pan_up_down": "Зсув угору / вниз",
"ui.3d_mouse_view_roll": "Крен",
"ui.3d_mouse_view_unmapped": "Вісь {n}",
"ui.3d_mouse_view_zoom": "Масштаб",
"ui.3d_mouse_waiting": "Очікування руху",
"ui.3d_mouse_wiggle": "Якщо 3D-миша підключена, трохи порухайте нею.",
"ui.3d_sketch": "3D-ескіз",
"ui.activate": "Активувати",
"ui.active_component": "Активний компонент",
@@ -1351,6 +1460,7 @@
"ui.blocked": "Заблоковані",
"ui.bodies": "Тіла",
"ui.bodies_all_if_none": "Тіла (усі, якщо не вибрано)",
"ui.bottom": "Знизу",
"ui.box": "Куб",
"ui.break": "Розірвати",
"ui.browser_bodies_one": "{count} тіло",
@@ -1503,6 +1613,7 @@
"ui.grid_follows_view_hint": "Бічні види показують сітку на вертикальній площині, повернутій до них, а не на землі",
"ui.ground_shadow": "Тінь на землі",
"ui.groupSelected": "Згрупувати виділене",
"ui.hatch": "Штрихування",
"ui.heading_add_ins": "НАДБУДОВИ",
"ui.heading_assemble": "СКЛАДАННЯ",
"ui.heading_box": "КУБ",
@@ -1793,59 +1904,6 @@
"ui.smooth_wheel_zoom": "Плавне масштабування колесом",
"ui.smooth_wheel_zoom_tip": "Вимкніть, щоб застосовувати кожну подію колеса негайно, без плавного завершення",
"ui.snap": "Прив'язка",
"ui.space_mouse_axes_reassigned": "Осі перепризначено в налаштуваннях, і це діє й поверх цього калібрування.",
"ui.space_mouse_calibrate": "Калібрувати…",
"ui.space_mouse_calibrate_tip": "Рухайте ковпачок так, як просить вікно, щоб SolidCraft дізнався, куди й наскільки далеко рухається кожна вісь цієї 3D-миші. Зберігається для цієї 3D-миші.",
"ui.space_mouse_calibrate_title": "Калібрування 3D-миші",
"ui.space_mouse_calibrated": "Відкалібровано",
"ui.space_mouse_calibration": "Калібрування 3D-миші",
"ui.space_mouse_calibration_done": "Готово. Збережіть його, щоб воно застосовувалося щоразу, коли підключено цю 3D-мишу.",
"ui.space_mouse_calibration_no_response": "Одна вісь майже не зрушила, тому її не можна відкалібрувати. Почніть спочатку й щоразу рухайте ковпачок далі.",
"ui.space_mouse_dead_zone": "Мертва зона",
"ui.space_mouse_dead_zone_tip": "Наскільки треба зсунути ковпачок, перш ніж зрушить вигляд, у частці від повного ходу. Збільште, якщо вигляд зміщується, коли ковпачок у спокої.",
"ui.space_mouse_driven_by": "Який відкалібрований рух пристрою керує цією віссю. Якщо вибрати рух, який уже використовує інша вісь, вони поміняються місцями.",
"ui.space_mouse_driven_by_label": "керується",
"ui.space_mouse_driver": "Драйвер 3D-миші",
"ui.space_mouse_driver_auto": "Автоматично",
"ui.space_mouse_driver_direct": "Напряму (hidraw)",
"ui.space_mouse_driver_only_direct": "Читання напряму з пристрою; spacenavd не запущено",
"ui.space_mouse_driver_only_spacenavd": "Читання через spacenavd, інших варіантів немає",
"ui.space_mouse_forget": "Забути калібрування",
"ui.space_mouse_hint": "Переміщуйте й обертайте вид разом; в ескізах доступні лише панорамування та масштаб.",
"ui.space_mouse_motion_rotation_x": "Обертання X",
"ui.space_mouse_motion_rotation_y": "Обертання Y",
"ui.space_mouse_motion_rotation_z": "Обертання Z",
"ui.space_mouse_motion_translation_x": "Зміщення X",
"ui.space_mouse_motion_translation_y": "Зміщення Y",
"ui.space_mouse_motion_translation_z": "Зміщення Z",
"ui.space_mouse_navigate": "Навігація за допомогою SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "3D-мишу не знайдено",
"ui.space_mouse_not_calibrated": "Не відкалібровано",
"ui.space_mouse_reading": "Показники",
"ui.space_mouse_reset_axes": "Скинути призначення осей",
"ui.space_mouse_step_lift": "Підніміть ковпачок угору, утримайте й відпустіть.",
"ui.space_mouse_step_press": "Натисніть ковпачок униз, утримайте й відпустіть.",
"ui.space_mouse_step_pull": "Потягніть ковпачок до себе, утримайте й відпустіть.",
"ui.space_mouse_step_push": "Штовхніть ковпачок від себе, утримайте й відпустіть.",
"ui.space_mouse_step_rest": "Приберіть руку з ковпачка й не торкайтеся його.",
"ui.space_mouse_step_slide_left": "Зсуньте ковпачок ліворуч, утримайте й відпустіть.",
"ui.space_mouse_step_slide_right": "Зсуньте ковпачок праворуч, утримайте й відпустіть.",
"ui.space_mouse_step_tilt_back": "Нахиліть ковпачок назад (передній край угору), утримайте й відпустіть.",
"ui.space_mouse_step_tilt_forward": "Нахиліть ковпачок уперед (передній край униз), утримайте й відпустіть.",
"ui.space_mouse_step_tilt_left": "Нахиліть ковпачок ліворуч (лівий край униз), утримайте й відпустіть.",
"ui.space_mouse_step_tilt_right": "Нахиліть ковпачок праворуч (правий край униз), утримайте й відпустіть.",
"ui.space_mouse_step_twist_clockwise": "Поверніть ковпачок за годинниковою стрілкою, утримайте й відпустіть.",
"ui.space_mouse_step_twist_counterclockwise": "Поверніть ковпачок проти годинникової стрілки, утримайте й відпустіть.",
"ui.space_mouse_view_axis": "Вісь вигляду",
"ui.space_mouse_view_off": "Вимк.",
"ui.space_mouse_view_off_tip": "Профіль драйвера 3D-миші вимикає цю вісь, тож її рух не рухає вигляд. Якщо вибрати тут рух іншої осі, вони поміняються місцями, і цей рух перестане рухати вигляд.",
"ui.space_mouse_view_orbit_sideways": "Орбіта ліворуч / праворуч",
"ui.space_mouse_view_orbit_up_down": "Орбіта вгору / вниз",
"ui.space_mouse_view_pan_sideways": "Зсув ліворуч / праворуч",
"ui.space_mouse_view_pan_up_down": "Зсув угору / вниз",
"ui.space_mouse_view_roll": "Крен",
"ui.space_mouse_view_unmapped": "Вісь {n}",
"ui.space_mouse_view_zoom": "Масштаб",
"ui.spacing": "Крок",
"ui.spacing_2": "Крок 2",
"ui.sphere": "Куля",
+111 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "Chọn…",
"Circular": "Tròn",
"Circumference": "Chu vi",
"Clockwise": "Theo chiều kim đồng hồ",
"Close draft": "Đóng bản nháp",
"Collisions": "Va chạm",
"Colour": "Màu sắc",
@@ -322,6 +323,7 @@
"Search commands": "Tìm kiếm lệnh",
"Search this library": "Tìm trong thư viện này",
"Section": "Mặt cắt",
"Section position": "Vị trí mặt cắt",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Chọn đối tượng khác: giữ nút chuột trái đứng yên trên mô hình (hoặc nhấp chuột phải › Chọn đối tượng khác…) để liệt kê mọi đối tượng dưới con trỏ; nhấp lại vào cùng vị trí đó để chọn đối tượng kế tiếp. Nhấp đúp chuột giữa để về Góc nhìn chuẩn (Tùy chọn cài đặt › Điều hướng).",
"Select bodies (a face picks its body)": "Chọn thân khối (chọn một mặt sẽ chọn cả thân khối)",
"Select bodies or faces instead": "Chọn thân khối hoặc bề mặt thay thế",
@@ -345,9 +347,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft đã đóng trước khi các thiết kế này được lưu. Bạn có thể mở bản tự động lưu gần nhất của chúng.",
"Specialise": "Chuyên biệt hóa",
"Speed": "Tốc độ",
"Square": "Hình vuông",
"Stand it up": "Dựng đứng đối tượng",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Dựng đứng đối tượng: xoay 90° quanh trục X, dưới dạng một tính năng Di chuyển có thể hoàn tác.",
"Stand this part up?": "Dựng đứng chi tiết này lên?",
"Start angle": "Góc bắt đầu",
"Start bulge": "Độ phình đầu",
"Start continuity": "Độ liên tục đầu",
"Step": "Bước",
@@ -412,6 +416,7 @@
"angle, height": "góc, chiều cao",
"appearance.dielectric_ior": "Chỉ số khúc xạ điện môi (IOR)",
"appearance.metallic": "Độ kim loại",
"appearance.only_available": "Chỉ các kiểu bề ngoài khả dụng",
"appearance.pbr_help": "Xem bề mặt này trong chế độ Kết xuất hoặc ảnh PNG sản phẩm. Không có khúc xạ hay truyền sáng.",
"appearance.pbr_opaque": "PBR không trong suốt",
"appearance.roughness": "Độ nhám bề mặt",
@@ -530,6 +535,10 @@
"cmd.file_insert_step": "Chèn tệp STEP",
"cmd.file_save_mesh": "Lưu dưới dạng lưới đa giác",
"cmd.file_slicers": "Phần mềm cắt lớp đã cài đặt",
"cmd.frame_member": "Thanh khung",
"cmd.frame_profile_insert": "Chèn tiết diện",
"cmd.frame_profile_list": "Thư viện tiết diện",
"cmd.frame_profile_properties": "Thuộc tính tiết diện",
"cmd.inspect_accessibility": "Phân tích khả năng gia công tiếp cận",
"cmd.inspect_center_of_mass": "Trọng tâm",
"cmd.inspect_clearance": "Khoảng cách an toàn",
@@ -569,6 +578,7 @@
"cmd.material_create": "Tạo vật liệu vật lý mới",
"cmd.material_delete": "Xóa vật liệu vật lý",
"cmd.material_edit": "Chỉnh sửa vật liệu vật lý",
"cmd.material_inspect": "Kiểm tra vật liệu vật lý",
"cmd.material_list": "Danh sách vật liệu",
"cmd.material_properties_directional": "Mô đun đàn hồi theo hướng",
"cmd.material_properties_validate": "Xác thực dữ liệu thuộc tính vật liệu",
@@ -1121,7 +1131,106 @@
"trace.stopped_by_contact": "Kết thúc tại mẫu {sample}: {a} sẽ xuyên qua {b}",
"trace.stopped_by_loop": "Kết thúc tại mẫu {sample}: vòng lặp qua {joint} không thể khép kín thêm được nữa",
"ui.1_degree_of_freedom": "1 bậc tự do",
"ui.3d_mouse_axes_reassigned": "Các trục được gán lại trong Tùy chọn, và điều đó vẫn áp dụng cùng với hiệu chuẩn này.",
"ui.3d_mouse_button": "Nút {number}",
"ui.3d_mouse_button_nothing": "Không có",
"ui.3d_mouse_button_settings": "Cài đặt chuột 3D",
"ui.3d_mouse_buttons": "Nút",
"ui.3d_mouse_buttons_hint": "Nhấn một nút trên chuột 3D để tìm nó ở đây, rồi chọn chức năng.",
"ui.3d_mouse_buttons_no_device": "Kết nối chuột 3D để chọn chức năng cho các nút của nó.",
"ui.3d_mouse_buttons_reset": "Khôi phục nút mặc định",
"ui.3d_mouse_calibrate": "Hiệu chuẩn…",
"ui.3d_mouse_calibrate_tip": "Di chuyển núm điều khiển theo yêu cầu của cửa sổ để SolidCraft biết mỗi trục của chuột 3D này đi theo hướng nào và xa bao nhiêu. Được lưu cho chuột 3D này.",
"ui.3d_mouse_calibrate_title": "Hiệu chuẩn chuột 3D",
"ui.3d_mouse_calibrate_wait": "Hãy di chuyển chuột 3D một lần để SolidCraft nhận ra nó.",
"ui.3d_mouse_calibrated": "Đã hiệu chuẩn",
"ui.3d_mouse_calibration": "Hiệu chuẩn chuột 3D",
"ui.3d_mouse_calibration_done": "Xong. Hãy lưu để dùng mỗi khi chuột 3D này được kết nối.",
"ui.3d_mouse_calibration_no_response": "Một trục hầu như không di chuyển nên không thể hiệu chuẩn. Hãy bắt đầu lại và di chuyển núm điều khiển xa hơn mỗi lần.",
"ui.3d_mouse_circles_help": "Các vòng tròn cho thấy thiết bị gửi gì sau khi hiệu chuẩn. Mô hình thử cho thấy khung nhìn làm gì với nó, sau đảo chiều, tốc độ và gán trục.",
"ui.3d_mouse_dead_zone": "Vùng chết",
"ui.3d_mouse_dead_zone_tip": "Núm điều khiển phải di chuyển bao xa thì khung nhìn mới di chuyển, tính theo tỷ lệ hành trình tối đa. Hãy tăng giá trị này nếu khung nhìn bị trôi khi núm đang đứng yên.",
"ui.3d_mouse_denied": "SolidCraft thấy một chuột 3D nhưng không được phép đọc nó. Xem ghi chú thiết lập chuột 3D để biết quy tắc udev.",
"ui.3d_mouse_denied_short": "Đã thấy, nhưng không đọc được",
"ui.3d_mouse_driven_by": "Chuyển động đã hiệu chuẩn nào của thiết bị điều khiển trục này. Chọn chuyển động mà trục khác đang dùng sẽ hoán đổi hai trục.",
"ui.3d_mouse_driven_by_label": "điều khiển bởi",
"ui.3d_mouse_driver": "Trình điều khiển chuột 3D",
"ui.3d_mouse_driver_auto": "Tự động",
"ui.3d_mouse_driver_direct": "Trực tiếp (hidraw)",
"ui.3d_mouse_driver_fallback": "Tạm thời đọc qua spacenavd: không mở được chính thiết bị.",
"ui.3d_mouse_driver_only_direct": "Đang đọc thiết bị trực tiếp; spacenavd không chạy",
"ui.3d_mouse_driver_only_spacenavd": "Đang đọc qua spacenavd, cách duy nhất có sẵn",
"ui.3d_mouse_forget": "Xóa hiệu chuẩn",
"ui.3d_mouse_ghost_left_viewport": "Khung nhìn bên trái",
"ui.3d_mouse_ghost_left_window": "Cửa sổ bên trái",
"ui.3d_mouse_ghost_position": "Vị trí",
"ui.3d_mouse_ghost_right_viewport": "Khung nhìn bên phải",
"ui.3d_mouse_hint": "Di chuyển và xoay khung nhìn đồng thời; môi trường phác thảo chỉ sử dụng dời khung nhìn và thu phóng.",
"ui.3d_mouse_kind_cap": "Trở về giữa",
"ui.3d_mouse_kind_question": "Đây là loại chuột 3D nào?",
"ui.3d_mouse_kind_trackball": "Bi xoay",
"ui.3d_mouse_looks_like_cap": "Giá trị giữ nguyên khi bạn giữ, giống thiết bị kiểu “Trở về giữa”.",
"ui.3d_mouse_looks_like_trackball": "Giá trị trở về ngay khi bạn ngừng di chuyển, giống bi xoay.",
"ui.3d_mouse_motion_rotation_x": "Xoay X",
"ui.3d_mouse_motion_rotation_y": "Xoay Y",
"ui.3d_mouse_motion_rotation_z": "Xoay Z",
"ui.3d_mouse_motion_translation_x": "Tịnh tiến X",
"ui.3d_mouse_motion_translation_y": "Tịnh tiến Y",
"ui.3d_mouse_motion_translation_z": "Tịnh tiến Z",
"ui.3d_mouse_navigate": "Điều hướng bằng chuột 3D",
"ui.3d_mouse_none": "Không tìm thấy chuột 3D",
"ui.3d_mouse_not_calibrated": "Chưa hiệu chuẩn",
"ui.3d_mouse_offer": "Hiệu chuẩn chuột 3D sẽ sửa các trục bị hoán đổi hoặc đảo ngược và chỉnh tốc độ cũng như cảm giác.",
"ui.3d_mouse_offer_never": "Không hiện lại",
"ui.3d_mouse_offer_swap": "Chuyển sang Hidraw (điều khiển trực tiếp)",
"ui.3d_mouse_reading": "Giá trị đọc",
"ui.3d_mouse_reset_axes": "Đặt lại phép gán trục",
"ui.3d_mouse_reset_view": "Đặt lại khung nhìn",
"ui.3d_mouse_restore": "Khôi phục mặc định chuột 3D",
"ui.3d_mouse_restore_ask": "Đặt cài đặt chuột 3D về mặc định? Hiệu chuẩn và gán nút được giữ nguyên.",
"ui.3d_mouse_section": "Chuột 3D",
"ui.3d_mouse_show_in_view": "Hiện chuột 3D trong khung nhìn 3D",
"ui.3d_mouse_show_in_view_tip": "Hình chuột 3D của bạn ở góc dưới bên trái, chuyển động theo bạn.",
"ui.3d_mouse_skip": "Thiết bị của tôi không làm được điều này",
"ui.3d_mouse_skip_tip": "Bỏ qua chuyển động này theo cả hai chiều; việc hiệu chuẩn sẽ không dùng nó.",
"ui.3d_mouse_speed": "Tốc độ chuột 3D",
"ui.3d_mouse_step_lift": "Nâng núm điều khiển lên, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_press": "Nhấn núm điều khiển xuống, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_pull": "Kéo núm điều khiển về phía bạn, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_push": "Đẩy núm điều khiển ra xa bạn, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_rest": "Bỏ tay khỏi núm điều khiển và để yên.",
"ui.3d_mouse_step_rest_trackball": "Bỏ tay khỏi viên bi và để yên.",
"ui.3d_mouse_step_ring_clockwise": "Xoay vòng cuộn theo chiều kim đồng hồ vài nấc.",
"ui.3d_mouse_step_ring_counterclockwise": "Xoay vòng cuộn ngược chiều kim đồng hồ vài nấc.",
"ui.3d_mouse_step_roll_away": "Lăn viên bi ra xa bạn vài lần, rồi để nó dừng lại.",
"ui.3d_mouse_step_roll_left": "Lăn viên bi sang trái vài lần, rồi để nó dừng lại.",
"ui.3d_mouse_step_roll_right": "Lăn viên bi sang phải vài lần, rồi để nó dừng lại.",
"ui.3d_mouse_step_roll_toward": "Lăn viên bi về phía bạn vài lần, rồi để nó dừng lại.",
"ui.3d_mouse_step_slide_left": "Trượt núm điều khiển sang trái, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_slide_right": "Trượt núm điều khiển sang phải, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_spin_clockwise": "Xoay viên bi theo chiều kim đồng hồ vài lần, rồi để nó dừng lại.",
"ui.3d_mouse_step_spin_counterclockwise": "Xoay viên bi ngược chiều kim đồng hồ vài lần, rồi để nó dừng lại.",
"ui.3d_mouse_step_tilt_away": "Nghiêng phần trên núm điều khiển ra xa bạn (cạnh gần bạn nhất nâng lên), giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_tilt_left": "Nghiêng núm điều khiển sang trái (cạnh trái hạ xuống), giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_tilt_right": "Nghiêng núm điều khiển sang phải (cạnh phải hạ xuống), giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_tilt_toward": "Nghiêng phần trên núm điều khiển về phía bạn (cạnh gần bạn nhất hạ xuống), giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_touch": "Đặt tay lên núm điều khiển nhưng không đẩy.",
"ui.3d_mouse_step_twist_clockwise": "Xoay núm điều khiển theo chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_step_twist_counterclockwise": "Xoay núm điều khiển ngược chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
"ui.3d_mouse_switch_cap": "Hiệu chuẩn theo kiểu trở về giữa",
"ui.3d_mouse_switch_trackball": "Hiệu chuẩn như bi xoay",
"ui.3d_mouse_view_axis": "Trục khung nhìn",
"ui.3d_mouse_view_off": "Tắt",
"ui.3d_mouse_view_off_tip": "Hồ sơ của trình điều khiển chuột 3D tắt trục này, nên chuyển động của nó không làm khung nhìn di chuyển. Chọn chuyển động của trục khác ở đây sẽ hoán đổi hai trục, và chuyển động đó không còn di chuyển khung nhìn.",
"ui.3d_mouse_view_orbit_sideways": "Xoay quanh trái / phải",
"ui.3d_mouse_view_orbit_up_down": "Xoay quanh lên / xuống",
"ui.3d_mouse_view_pan_sideways": "Kéo trái / phải",
"ui.3d_mouse_view_pan_up_down": "Kéo lên / xuống",
"ui.3d_mouse_view_roll": "Lăn",
"ui.3d_mouse_view_unmapped": "Trục {n}",
"ui.3d_mouse_view_zoom": "Thu phóng",
"ui.3d_mouse_waiting": "Đang chờ chuyển động",
"ui.3d_mouse_wiggle": "Nếu bạn đã cắm một chiếc, hãy lắc nhẹ nó.",
"ui.3d_sketch": "3D Phác thảo",
"ui.activate": "Kích hoạt",
"ui.active_component": "Cụm chi tiết đang kích hoạt",
@@ -1351,6 +1460,7 @@
"ui.blocked": "Bị chặn",
"ui.bodies": "Các thân khối",
"ui.bodies_all_if_none": "Thân khối (tất cả nếu không chọn)",
"ui.bottom": "Dưới",
"ui.box": "Khối hộp",
"ui.break": "Ngắt",
"ui.browser_bodies_one": "{count} thân khối",
@@ -1503,6 +1613,7 @@
"ui.grid_follows_view_hint": "Các góc nhìn cạnh hiển thị lưới trên mặt phẳng thẳng đứng hướng về phía camera thay vì trên mặt sàn",
"ui.ground_shadow": "Bóng đổ mặt sàn",
"ui.groupSelected": "Gộp nhóm vùng chọn",
"ui.hatch": "Gạch mặt cắt",
"ui.heading_add_ins": "TIỆN ÍCH MỞ RỘNG",
"ui.heading_assemble": "LẮP RÁP",
"ui.heading_box": "KHỐI HỘP",
@@ -1793,34 +1904,6 @@
"ui.smooth_wheel_zoom": "Thu phóng mượt bằng con lăn",
"ui.smooth_wheel_zoom_tip": "Tắt tùy chọn này để áp dụng từng sự kiện con lăn ngay lập tức mà không có hiệu ứng làm mượt chuyển động",
"ui.snap": "Bắt điểm",
"ui.space_mouse_calibrate": "Hiệu chuẩn…",
"ui.space_mouse_calibrate_tip": "Di chuyển núm điều khiển theo yêu cầu của cửa sổ để SolidCraft biết mỗi trục của chuột 3D này đi theo hướng nào và xa bao nhiêu. Được lưu cho chuột 3D này.",
"ui.space_mouse_calibrate_title": "Hiệu chuẩn chuột 3D",
"ui.space_mouse_calibrated": "Đã hiệu chuẩn",
"ui.space_mouse_calibration": "Hiệu chuẩn chuột 3D",
"ui.space_mouse_calibration_done": "Xong. Hãy lưu để dùng mỗi khi chuột 3D này được kết nối.",
"ui.space_mouse_calibration_no_response": "Một trục hầu như không di chuyển nên không thể hiệu chuẩn. Hãy bắt đầu lại và di chuyển núm điều khiển xa hơn mỗi lần.",
"ui.space_mouse_dead_zone": "Vùng chết",
"ui.space_mouse_dead_zone_tip": "Núm điều khiển phải di chuyển bao xa thì góc nhìn mới di chuyển, tính theo tỷ lệ hành trình tối đa. Hãy tăng giá trị này nếu góc nhìn bị trôi khi núm đang đứng yên.",
"ui.space_mouse_forget": "Xóa hiệu chuẩn",
"ui.space_mouse_hint": "Di chuyển và xoay khung nhìn đồng thời; môi trường phác thảo chỉ sử dụng dời góc nhìn và thu phóng.",
"ui.space_mouse_navigate": "Điều hướng bằng SpaceNavigator / SpaceMouse",
"ui.space_mouse_none": "Không tìm thấy chuột 3D",
"ui.space_mouse_not_calibrated": "Chưa hiệu chuẩn",
"ui.space_mouse_reading": "Giá trị đọc",
"ui.space_mouse_step_lift": "Nâng núm điều khiển lên, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_press": "Nhấn núm điều khiển xuống, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_pull": "Kéo núm điều khiển về phía bạn, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_push": "Đẩy núm điều khiển ra xa bạn, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_rest": "Bỏ tay khỏi núm điều khiển và để yên.",
"ui.space_mouse_step_slide_left": "Trượt núm điều khiển sang trái, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_slide_right": "Trượt núm điều khiển sang phải, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_tilt_back": "Nghiêng núm điều khiển về sau (cạnh trước nâng lên), giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_tilt_forward": "Nghiêng núm điều khiển về trước (cạnh trước hạ xuống), giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_tilt_left": "Nghiêng núm điều khiển sang trái (cạnh trái hạ xuống), giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_tilt_right": "Nghiêng núm điều khiển sang phải (cạnh phải hạ xuống), giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_twist_clockwise": "Xoay núm điều khiển theo chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
"ui.space_mouse_step_twist_counterclockwise": "Xoay núm điều khiển ngược chiều kim đồng hồ, giữ nguyên, rồi thả ra.",
"ui.spacing": "Khoảng cách",
"ui.spacing_2": "Khoảng cách 2",
"ui.sphere": "Hình cầu",
+54 -28
View File
@@ -72,6 +72,7 @@
"Choose…": "選擇…",
"Circular": "環形",
"Circumference": "周長",
"Clockwise": "順時針",
"Close draft": "關閉草稿",
"Collisions": "碰撞",
"Colour": "顏色",
@@ -321,6 +322,7 @@
"Search commands": "搜尋命令",
"Search this library": "搜尋此零件庫",
"Section": "剖面",
"Section position": "截面位置",
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "「選擇其他」:在模型上按住左鍵(或右鍵點擊 › 選擇其他…)列出游標下的所有物件;在同一位置再次點擊可選擇該處的下一個物件。雙擊中鍵轉到主檢視(偏好設定 › 導覽)。",
"Select bodies (a face picks its body)": "選擇實體(點擊面即選擇其所屬實體)",
"Select bodies or faces instead": "改為選擇實體或面",
@@ -344,9 +346,11 @@
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft 在這些設計儲存前已關閉。可以開啟它們的上次自動儲存。",
"Specialise": "專用操作",
"Speed": "速度",
"Square": "正方形",
"Stand it up": "將其立起",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "將其立起:繞 X 軸旋轉四分之一週,作為一個可復原的移動特徵。",
"Stand this part up?": "立起此零件?",
"Start angle": "起始角度",
"Start bulge": "起始鼓度",
"Start continuity": "起始連續性",
"Step": "步驟",
@@ -411,6 +415,7 @@
"angle, height": "角度、高度",
"appearance.dielectric_ior": "介電折射率",
"appearance.metallic": "金屬度",
"appearance.only_available": "僅顯示可用外觀",
"appearance.pbr_help": "在彩現工作區或產品 PNG 中檢視此表面。不支援折射或透射。",
"appearance.pbr_opaque": "不透明 PBR",
"appearance.roughness": "粗糙度",
@@ -529,6 +534,10 @@
"cmd.file_insert_step": "插入 STEP",
"cmd.file_save_mesh": "另存為網格",
"cmd.file_slicers": "已安裝的切片軟體",
"cmd.frame_member": "框架構件",
"cmd.frame_profile_insert": "插入型材",
"cmd.frame_profile_list": "型材庫",
"cmd.frame_profile_properties": "型材屬性",
"cmd.inspect_accessibility": "可達性分析",
"cmd.inspect_center_of_mass": "質心",
"cmd.inspect_clearance": "間隙",
@@ -568,6 +577,7 @@
"cmd.material_create": "新建物理材質",
"cmd.material_delete": "刪除物理材質",
"cmd.material_edit": "編輯物理材質",
"cmd.material_inspect": "檢查物理材質",
"cmd.material_list": "列出材質",
"cmd.material_properties_directional": "方向彈性模量",
"cmd.material_properties_validate": "驗證物理材質資料",
@@ -1120,7 +1130,50 @@
"trace.stopped_by_contact": "在取樣 {sample} 處結束:{a} 會穿過 {b}",
"trace.stopped_by_loop": "在取樣 {sample} 處結束:經過 {joint} 的環路無法再閉合",
"ui.1_degree_of_freedom": "1 個自由度",
"ui.3d_mouse_calibrate": "校準…",
"ui.3d_mouse_calibrate_tip": "按視窗提示移動搖桿帽,SolidCraft 會記住這個 3D 滑鼠每個軸的方向和行程。校準結果按此 3D 滑鼠儲存。",
"ui.3d_mouse_calibrate_title": "校準 3D 滑鼠",
"ui.3d_mouse_calibrate_wait": "請移動一次 3D 滑鼠,讓 SolidCraft 能夠辨識它。",
"ui.3d_mouse_calibrated": "已校準",
"ui.3d_mouse_calibration": "3D 滑鼠校準",
"ui.3d_mouse_calibration_done": "完成。儲存後,每次連線此 3D 滑鼠都會使用它。",
"ui.3d_mouse_calibration_no_response": "有一個軸幾乎沒有移動,因此無法校準。請重新開始,並且每次把搖桿帽移得更遠。",
"ui.3d_mouse_circles_help": "圓圈顯示裝置在校準後送出的內容。測試模型顯示檢視在反向、速度與軸指定之後如何處理它。",
"ui.3d_mouse_dead_zone": "死區",
"ui.3d_mouse_dead_zone_tip": "檢視開始移動前搖桿帽需要移動的距離,以其全行程的比例表示。如果搖桿帽靜止時檢視仍在漂移,請調大。",
"ui.3d_mouse_denied": "SolidCraft 看得到 3D 滑鼠,但沒有權限讀取。udev 規則請見 3D 滑鼠的設定說明。",
"ui.3d_mouse_denied_short": "已找到,但無法讀取",
"ui.3d_mouse_driver_fallback": "目前改透過 spacenavd 讀取:無法開啟裝置本身。",
"ui.3d_mouse_forget": "清除校準",
"ui.3d_mouse_ghost_left_viewport": "視埠左側",
"ui.3d_mouse_ghost_left_window": "視窗左側",
"ui.3d_mouse_ghost_position": "位置",
"ui.3d_mouse_ghost_right_viewport": "視埠右側",
"ui.3d_mouse_hint": "同時移動和旋轉檢視;草圖中僅支援平移和縮放。",
"ui.3d_mouse_navigate": "使用 3D 滑鼠導覽",
"ui.3d_mouse_none": "未找到 3D 滑鼠",
"ui.3d_mouse_not_calibrated": "未校準",
"ui.3d_mouse_offer": "校準 3D 滑鼠可以修正互換或反向的軸,並調整它的速度和手感。",
"ui.3d_mouse_offer_never": "不再顯示",
"ui.3d_mouse_offer_swap": "切換到 Hidraw(直接驅動)",
"ui.3d_mouse_reading": "讀值",
"ui.3d_mouse_reset_view": "重設檢視",
"ui.3d_mouse_restore": "還原 3D 滑鼠預設值",
"ui.3d_mouse_restore_ask": "要將 3D 滑鼠設定還原為預設值嗎?校準和按鈕對應會保留。",
"ui.3d_mouse_speed": "3D 滑鼠速度",
"ui.3d_mouse_step_lift": "向上提起搖桿帽,保持住,然後鬆開。",
"ui.3d_mouse_step_press": "向下按壓搖桿帽,保持住,然後鬆開。",
"ui.3d_mouse_step_pull": "把搖桿帽拉向自己,保持住,然後鬆開。",
"ui.3d_mouse_step_push": "把搖桿帽推離自己,保持住,然後鬆開。",
"ui.3d_mouse_step_rest": "請把手從搖桿帽上移開,讓它保持靜止。",
"ui.3d_mouse_step_slide_left": "向左推動搖桿帽,保持住,然後鬆開。",
"ui.3d_mouse_step_slide_right": "向右推動搖桿帽,保持住,然後鬆開。",
"ui.3d_mouse_step_tilt_left": "向左傾斜搖桿帽(左緣向下),保持住,然後鬆開。",
"ui.3d_mouse_step_tilt_right": "向右傾斜搖桿帽(右緣向下),保持住,然後鬆開。",
"ui.3d_mouse_step_twist_clockwise": "順時針扭轉搖桿帽,保持住,然後鬆開。",
"ui.3d_mouse_step_twist_counterclockwise": "逆時針扭轉搖桿帽,保持住,然後鬆開。",
"ui.3d_mouse_waiting": "等待移動",
"ui.3d_mouse_wiggle": "如果已插上,請輕輕晃動一下。",
"ui.3d_sketch": "3D 草圖",
"ui.activate": "啟用",
"ui.active_component": "作用中元件",
@@ -1502,6 +1555,7 @@
"ui.grid_follows_view_hint": "在側檢視中,將網格顯示在朝向該檢視的豎直平面上,而不是地面上",
"ui.ground_shadow": "地面陰影",
"ui.groupSelected": "群組所選",
"ui.hatch": "剖面線",
"ui.heading_add_ins": "增益集",
"ui.heading_assemble": "組裝",
"ui.heading_box": "長方體",
@@ -1792,34 +1846,6 @@
"ui.smooth_wheel_zoom": "平滑滾輪縮放",
"ui.smooth_wheel_zoom_tip": "關閉後每個滾輪事件立即生效,不做平滑延遲",
"ui.snap": "鎖點",
"ui.space_mouse_calibrate": "校準…",
"ui.space_mouse_calibrate_tip": "按視窗提示移動搖桿帽,SolidCraft 會記住這個 3D 滑鼠每個軸的方向和行程。校準結果按此 3D 滑鼠儲存。",
"ui.space_mouse_calibrate_title": "校準 3D 滑鼠",
"ui.space_mouse_calibrated": "已校準",
"ui.space_mouse_calibration": "3D 滑鼠校準",
"ui.space_mouse_calibration_done": "完成。儲存後,每次連線此 3D 滑鼠都會使用它。",
"ui.space_mouse_calibration_no_response": "有一個軸幾乎沒有移動,因此無法校準。請重新開始,並且每次把搖桿帽移得更遠。",
"ui.space_mouse_dead_zone": "死區",
"ui.space_mouse_dead_zone_tip": "檢視開始移動前搖桿帽需要移動的距離,以其全行程的比例表示。如果搖桿帽靜止時檢視仍在漂移,請調大。",
"ui.space_mouse_forget": "清除校準",
"ui.space_mouse_hint": "同時移動和旋轉檢視;草圖中僅支援平移和縮放。",
"ui.space_mouse_navigate": "使用 SpaceNavigator / SpaceMouse 導覽",
"ui.space_mouse_none": "未找到 3D 滑鼠",
"ui.space_mouse_not_calibrated": "未校準",
"ui.space_mouse_reading": "讀值",
"ui.space_mouse_step_lift": "向上提起搖桿帽,保持住,然後鬆開。",
"ui.space_mouse_step_press": "向下按壓搖桿帽,保持住,然後鬆開。",
"ui.space_mouse_step_pull": "把搖桿帽拉向自己,保持住,然後鬆開。",
"ui.space_mouse_step_push": "把搖桿帽推離自己,保持住,然後鬆開。",
"ui.space_mouse_step_rest": "請把手從搖桿帽上移開,讓它保持靜止。",
"ui.space_mouse_step_slide_left": "向左推動搖桿帽,保持住,然後鬆開。",
"ui.space_mouse_step_slide_right": "向右推動搖桿帽,保持住,然後鬆開。",
"ui.space_mouse_step_tilt_back": "向後傾斜搖桿帽(前緣向上),保持住,然後鬆開。",
"ui.space_mouse_step_tilt_forward": "向前傾斜搖桿帽(前緣向下),保持住,然後鬆開。",
"ui.space_mouse_step_tilt_left": "向左傾斜搖桿帽(左緣向下),保持住,然後鬆開。",
"ui.space_mouse_step_tilt_right": "向右傾斜搖桿帽(右緣向下),保持住,然後鬆開。",
"ui.space_mouse_step_twist_clockwise": "順時針扭轉搖桿帽,保持住,然後鬆開。",
"ui.space_mouse_step_twist_counterclockwise": "逆時針扭轉搖桿帽,保持住,然後鬆開。",
"ui.spacing": "間距",
"ui.spacing_2": "間距 2",
"ui.sphere": "球體",
+841 -56
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -1,9 +1,14 @@
//! Built-in physical materials: densities in g/cm³ and sRGB colours.
/// The default material's name: what a body without a usable material of its own is made of.
/// Assigning it clears a body's assignment.
pub const DEFAULT_MATERIAL: &str = "Default";
/// Density of the default material (g/cm³).
pub const DEFAULT_DENSITY: f64 = 1.29;
/// Physical materials: name and density (g/cm³).
pub const MATERIALS: [(&str, f64); 14] = [
("Default", DEFAULT_DENSITY),
(DEFAULT_MATERIAL, DEFAULT_DENSITY),
("Steel", 7.85),
("Stainless Steel", 8.0),
("Aluminum", 2.70),
+79
View File
@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""Packs the boat for the 3D mouse test model in Preferences into assets/models/test_boat.bin, from a
binary STL of the low-poly boat (about 850 triangles). Needs numpy.
python3 assets/models/make_test_boat.py SOURCE.stl assets/models/test_boat.bin
No reduction happens here: the vertices are welded, and each welded vertex is split by the angle
between the faces that meet there (more than 40 degrees apart is a crease), so smooth surfaces share
a vertex normal and edges stay sharp. The model is centred and scaled into a unit box, then
quantised. File layout, little-endian: u32 vertex count, u32 triangle count, f32 centre (x, y, z),
f32 half size of the longest side; then per vertex its position as i16 x 3 (32767 is the half size)
and its normal as i8 x 3 (127 is a unit); then per triangle three u16 vertex indices. The model
sits with z up.
"""
import struct
import sys
import numpy as np
CREASE_DEGREES = 40.0
def main() -> None:
source, target = sys.argv[1], sys.argv[2]
data = open(source, "rb").read()
count = struct.unpack_from("<I", data, 80)[0]
record = np.dtype([("n", "<f4", 3), ("v", "<f4", (3, 3)), ("attr", "<u2")])
tris = np.frombuffer(data, dtype=record, count=count, offset=84)["v"].astype(np.float64)
points, inverse = np.unique(np.round(tris.reshape(-1, 3), 3), axis=0, return_inverse=True)
faces = inverse.reshape(-1, 3)
keep = (faces[:, 0] != faces[:, 1]) & (faces[:, 1] != faces[:, 2]) & (faces[:, 0] != faces[:, 2])
faces = faces[keep]
a, b, c = points[faces[:, 0]], points[faces[:, 1]], points[faces[:, 2]]
cross = np.cross(b - a, c - a)
area = np.linalg.norm(cross, axis=1)
ok = area > 1e-9
faces, cross, area = faces[ok], cross[ok], area[ok]
face_normal = cross / area[:, None]
# Split each welded vertex into one vertex per group of faces that agree (within the crease angle).
cos_limit = np.cos(np.radians(CREASE_DEGREES))
corners = {}
for f, tri in enumerate(faces):
for k in range(3):
corners.setdefault(int(tri[k]), []).append((f, k))
out_points, out_normals, index = [], [], np.zeros(faces.shape, dtype=np.int64)
for v, incident in corners.items():
groups = [] # each: [summed normal, [(face, corner)]]
for f, k in incident:
n = face_normal[f]
for g in groups:
mean = g[0] / np.linalg.norm(g[0])
if mean @ n > cos_limit:
g[0] = g[0] + n * area[f]
g[1].append((f, k))
break
else:
groups.append([n * area[f], [(f, k)]])
for total, members in groups:
length = np.linalg.norm(total)
out_normals.append(total / length if length > 0 else np.array([0.0, 0.0, 1.0]))
out_points.append(points[v])
for f, k in members:
index[f, k] = len(out_points) - 1
out_points, out_normals = np.array(out_points), np.array(out_normals)
low, high = out_points.min(axis=0), out_points.max(axis=0)
centre, half = (low + high) / 2.0, float((high - low).max() / 2.0)
positions = np.clip(np.round((out_points - centre) / half * 32767.0), -32767, 32767).astype("<i2")
normals = np.clip(np.round(out_normals * 127.0), -127, 127).astype("i1")
assert len(out_points) <= 65535
with open(target, "wb") as out:
out.write(struct.pack("<II3ff", len(out_points), len(index), *centre.tolist(), half))
for p, n in zip(positions, normals):
out.write(p.tobytes() + n.tobytes())
out.write(index.astype("<u2").tobytes())
print(f"{len(out_points)} vertices, {len(index)} triangles, centre {centre}, half size {half}")
if __name__ == "__main__":
main()
Binary file not shown.
+6
View File
@@ -0,0 +1,6 @@
# Recipe and generator receipts hash canonical bytes on every platform.
*.json text eol=lf
*.py text eol=lf
*.md text eol=lf
*.png binary
*.hdr binary
+14
View File
@@ -0,0 +1,14 @@
# Runtime render catalog exports
These JSON files are necessary runtime exports of the editable authoring catalogs in
`docs/render-materials/`. Do not edit both copies. `python -B tools/render-library/generate.py`
copies the original bytes and updates the Rust lookup; `--check` verifies exact equality.
The application includes these asset exports, never files from the documentation tree.
The 192 texture maps and 24 HDR files are separate from this metadata. They are included
only with the engine's `bundled-render-library` feature. Desktop builds enable it by
default. Standalone CLI and web builds do not; use their same-named feature to opt in.
Discovery and uniform appearances remain available without the image pack. Requests
requiring missing bytes fail explicitly before document mutation; no network is used.
GPU/UI test targets opt in deliberately. Exercise the engine's no-pack negative tests
separately from its feature-enabled resource tests.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+759
View File
@@ -0,0 +1,759 @@
{
"metadata": {
"schema_version": "0.2.0",
"language": "en",
"status": "specified_unbound",
"provenance_id": "ORIGINAL-2026-10-CATALOG"
},
"items": [
{
"id": "Q-001",
"name": "Interactive Draft",
"status": "specified_unbound",
"goal": "preview",
"resolution": {
"width": 960,
"height": 540
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 8,
"maximum_samples": 64,
"relative_error_target": 0.12,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 4,
"maximum_transmission_depth": 2,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_buffer",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-081",
"CAP-083",
"CAP-085"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-002",
"name": "Balanced Product",
"status": "specified_unbound",
"goal": "presentation",
"resolution": {
"width": 1600,
"height": 900
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 32,
"maximum_samples": 512,
"relative_error_target": 0.035,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 8,
"maximum_transmission_depth": 6,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": true,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_buffer",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-065",
"CAP-073",
"CAP-081",
"CAP-083",
"CAP-085"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-003",
"name": "Product Portfolio",
"status": "specified_unbound",
"goal": "presentation",
"resolution": {
"width": 2560,
"height": 1440
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 64,
"maximum_samples": 2048,
"relative_error_target": 0.015,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 12,
"maximum_transmission_depth": 10,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": true,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_buffer",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-065",
"CAP-073",
"CAP-081",
"CAP-083",
"CAP-085"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-004",
"name": "Glass and Layers",
"status": "specified_unbound",
"goal": "optics",
"resolution": {
"width": 2560,
"height": 1440
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 128,
"maximum_samples": 4096,
"relative_error_target": 0.01,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 20,
"maximum_transmission_depth": 16,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": true,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-025",
"CAP-026",
"CAP-027",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-065",
"CAP-073",
"CAP-074",
"CAP-081",
"CAP-083",
"CAP-085"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-005",
"name": "LED Optical Study",
"status": "specified_unbound",
"goal": "optics",
"resolution": {
"width": 2560,
"height": 1440
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 128,
"maximum_samples": 4096,
"relative_error_target": 0.01,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 20,
"maximum_transmission_depth": 16,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-025",
"CAP-026",
"CAP-027",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-074",
"CAP-081",
"CAP-083",
"CAP-085"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-006",
"name": "Lunar Hero Master",
"status": "specified_unbound",
"goal": "lunar",
"resolution": {
"width": 3840,
"height": 2160
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 256,
"maximum_samples": 8192,
"relative_error_target": 0.005,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 24,
"maximum_transmission_depth": 20,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-025",
"CAP-026",
"CAP-027",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-074",
"CAP-081",
"CAP-083",
"CAP-085",
"CAP-105"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-007",
"name": "Lunar Sample Macro",
"status": "specified_unbound",
"goal": "lunar",
"resolution": {
"width": 3072,
"height": 3072
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 256,
"maximum_samples": 8192,
"relative_error_target": 0.005,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 24,
"maximum_transmission_depth": 20,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-025",
"CAP-026",
"CAP-027",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-074",
"CAP-081",
"CAP-083",
"CAP-085",
"CAP-105"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-008",
"name": "Animation Preview",
"status": "specified_unbound",
"goal": "animation",
"resolution": {
"width": 1280,
"height": 720
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 16,
"maximum_samples": 256,
"relative_error_target": 0.06,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 8,
"maximum_transmission_depth": 6,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": true
},
"motion": {
"enabled": true,
"shutter_fraction": 0.5,
"temporal_samples": 8,
"requires_approved_bake": true,
"sample_budget_policy": "One shutter-time sample per camera path, stratified over temporal_samples intervals; temporal_samples does not multiply the spatial sample budget.",
"shutter_alignment": "centered_on_frame_time",
"frame_rate_binding": "positive_finite_shot_frame_rate_hz_required"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-053",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-074",
"CAP-078",
"CAP-081",
"CAP-083",
"CAP-085",
"CAP-111",
"CAP-112",
"CAP-119"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-009",
"name": "Animation Final",
"status": "specified_unbound",
"goal": "animation",
"resolution": {
"width": 3840,
"height": 2160
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 128,
"maximum_samples": 4096,
"relative_error_target": 0.012,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 16,
"maximum_transmission_depth": 12,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": true
},
"motion": {
"enabled": true,
"shutter_fraction": 0.5,
"temporal_samples": 32,
"requires_approved_bake": true,
"sample_budget_policy": "One shutter-time sample per camera path, stratified over temporal_samples intervals; temporal_samples does not multiply the spatial sample budget.",
"shutter_alignment": "centered_on_frame_time",
"frame_rate_binding": "positive_finite_shot_frame_rate_hz_required"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-053",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-074",
"CAP-078",
"CAP-081",
"CAP-083",
"CAP-085",
"CAP-105",
"CAP-111",
"CAP-112",
"CAP-119"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
},
{
"id": "Q-010",
"name": "Reference Convergence",
"status": "specified_unbound",
"goal": "reference",
"resolution": {
"width": 2048,
"height": 2048
},
"integrator_intent": "path_tracing",
"sampling": {
"minimum_samples": 1024,
"maximum_samples": 16384,
"relative_error_target": 0.002,
"error_estimator": "Versioned relative standard error of scene-linear luminance with a documented dark-signal floor; adapter must bind an estimator before use.",
"maximum_samples_action": "Finish with an unconverged flag when the target is not met.",
"scope": "Estimated Monte Carlo uncertainty for the bound estimator, not total optical error, finite-depth bias, rare-path coverage or temporal error.",
"stopping_rule_binding": "Require a versioned pixel/tile aggregation rule and dark-signal floor before execution."
},
"transport": {
"maximum_depth": 32,
"maximum_transmission_depth": 24,
"russian_roulette_after_depth": 3,
"roulette_policy": "Compensated stochastic termination; finite depth is a declared truncation."
},
"denoising": {
"enabled": false,
"preserve_raw": true,
"temporal_validation_required": false
},
"motion": {
"enabled": false,
"shutter_fraction": 0.0,
"temporal_samples": 0,
"requires_approved_bake": false,
"sample_budget_policy": "No time integration.",
"shutter_alignment": "instantaneous",
"frame_rate_binding": "not_applicable"
},
"output": {
"master": "linear_exr",
"presentation": "png",
"color_transform": "project_declared",
"exposure_policy": "manual_locked"
},
"seed": 21026,
"required_capabilities": [
"CAP-001",
"CAP-002",
"CAP-003",
"CAP-004",
"CAP-005",
"CAP-006",
"CAP-025",
"CAP-026",
"CAP-027",
"CAP-057",
"CAP-058",
"CAP-059",
"CAP-073",
"CAP-074",
"CAP-081",
"CAP-083",
"CAP-085"
],
"budget_policy": "Resolve memory and time against the selected executor; never switch devices or downgrade silently.",
"baseline_compatibility": "Future authoring target; resolve every field against the current executor before use.",
"validation": [
"Measure convergence in dark regolith and bright metal or glass regions.",
"Compare raw and finished results; preserve labels, fibers and thin wires.",
"Report elapsed time, peak memory and incomplete quality targets.",
"Compare multiple seeds and increased sample/depth budgets; an estimated noise threshold alone does not establish reference accuracy."
],
"provenance_id": "ORIGINAL-2026-10-CATALOG",
"seed_policy": "Base seed; bind a versioned frame/pixel/sample/time-stratum stream policy and record it in the render manifest."
}
]
}
File diff suppressed because it is too large Load Diff
+153
View File
@@ -0,0 +1,153 @@
# Physical material integration contract
This resource slice is based on `6d79d44fa403581e4508d7c9df5e163ff85085bf`.
It does not edit the parallel renderer implementation or the native document
format. The native candidate-card schema is in
[`crates/doc/src/physical_material.rs`](../../../crates/doc/src/physical_material.rs).
The personal integration branch separately implements design-owned cards and
library v2; capability checks must use the actual executing build.
## Three independent representations
1. **Extended reference profile:** the authoritative physical data with quantities,
individual source values, SI conversions, conditions, ranges, uncertainty,
directions and unresolved applicability. It is not a solver material model.
2. **Native schema-1 card:** only the supported scalar point properties and complete
admitted elastic blocks. Unsupported data remains in the extended profile and
is listed in `not_projected`; it is never silently discarded.
3. **Assigned document material:** the application's material object, its explicit
mass density, optional physical card and optional appearance. A profile link in
the render catalog is not evidence that this object has been created or assigned.
`exports/native-cards.json` is a bundle of individually valid card objects plus
projection metadata. It is not `solidcraft-library/1`, `solidcraft-library/2`, or
an application command script. Do not pass its whole envelope as a single card.
## Supported current commands
The baseline read-only API is:
```json
{"command":"material.properties.validate","params":{"card":"<individual card object>"}}
```
Replace the placeholder string with the actual JSON object. It validates
numerical/structural consistency and reports missing conditions and source review;
it does not assign, persist or authenticate the data.
The parallel personal runtime's persistence contract, inspected in its engine
source, is `material.create` / `material.edit` with an explicit `physical_card`:
```text
material.create {name, density_g_cm3, physical_card}
material.edit {material, physical_card}
```
A host must first verify that its registered command specification supports
`physical_card`; older command implementations may ignore fields they do not know.
The generated `prepare` plan names this prerequisite and is not executed here.
It does not issue `material.assign`, guess body names or create geometry.
On the persistence implementation, **editing the physical card does not silently
replace the document's mass density**. A discrepancy between card density in kg/m3
and assigned density in g/cm3 must be shown and explicitly resolved. The creation
plan uses the same selected point density in both fields. A range, missing density
or unselected formulation cannot create a falsely precise material.
For an existing body, retain its current physical material until a user explicitly
selects a compatible reference profile. Changing a visual finish must retain
physical fields, mass, joint geometry, body dimensions and material axes. Conversely,
changing an engineering card must not recolor the body or replace its PBR maps.
All document mutations must use the engine command/undo transaction, not direct
JSON mutation or a Python runtime dependency.
## Projection policy
The current native schema permits density, isotropic/orthotropic elasticity,
scalar tensile/compressive/yield/shear strengths, thermal conductivity/expansion
and specific heat. This exporter deliberately does not manufacture missing
constants to fill those fields.
- An isotropic block requires a same-source compatible E and Poisson ratio. Where
Poisson ratio is explicitly derived from the same source's E and G, the formula
and derived status are retained and identified in the native condition text.
- A single directional Young modulus is not a complete orthotropic tensor. Wood
flexural values and printed XY/Z coupon values stay directional in the extended
data; no isotropic substitute is introduced.
- Lower/upper bounds and intervals do not become native nominal point values.
They remain listed omissions. No midpoint or minimum-as-typical substitution.
- Directional thermal data is not copied into three axes. A repeated thermal
diagonal is allowed only for a profile explicitly marked as an isotropic
reference approximation, which the native state text discloses.
- Each native property retains its own temperature, moisture and strain-rate
fields. Other source conditions, including an interval or test method, are
retained in its state text. Unknown conditions are null, not assumed room values.
- Electrical data, hardness scales, radiative properties, porosity and transition
temperatures remain in the richer profile. Typed runtime support for these
quantities is separate work; preserve the extended data when designing it.
A structurally valid empty card is not a usable numerical material. The bundle
labels it `extended_data_only`; the coverage and native report separate those
eleven records from the 48 nonempty partial cards.
## Layers, compounds and analogues
`bindings.json` states whether a concept is a bulk body, layer, deposit, treated
surface, composite, textile, panel, granular material, device or assembly. Read
both `physical_role` and every reference's `relation` before offering assignment.
A constituent reference cannot be directly used as the effective mass density or
stiffness of a foam, laminate, populated PCB, honeycomb or multi-layer blanket.
Layer properties need thickness/coverage; homogenized properties need an explicit
construction and admissible model. Do not turn an unqualified comparison into a
default because it is the first reference in a list.
Lunar-like concepts use named terrestrial simulants only where stated. Bulk
packing density differs from grain density. Angle of repose is not substituted
for an internal friction angle, and terrestrial packing data does not certify
vacuum, gravity or thermal behavior on the Moon.
## Future library v2 export
The integrated runtime's `solidcraft_io::catalog` owns library serialization.
It computes entry revisions from its compact serde representation of provenance
and definition. Do not calculate an assumed equivalent Python hash or hand-write
a catalog revision: cross-language float spelling and omitted fields can differ.
The next runtime adapter should construct `ValidatedCard` through native admission,
use the runtime's catalog types, retain provenance and the extended-profile ID,
and generate the canonical revision through `Entry::new` / `content_revision`.
Tests must cover import, undo/redo, saved reopen, unchanged appearance, density
comparison and retention of unsupported extended fields. This slice performs
none of those document mutations and does not claim them tested.
## Validation boundaries
The supplied runner can invoke a trusted installed CLI only for read-only card
validation. The report identifies its binary hash and explicitly says that it
was not rebuilt by this gate. Numeric admission is not a source-license decision,
a physical experiment, a constitutive validation, a safety factor, a whole-app
build, or evidence of physics-driven rendering.
## Safe native library exporter
`tools/export_library.py --acknowledge-reference` prepares a non-executing plan
for the 46 point-density references and identifies 13 omitted profiles. Omission
is explicit: the application requires a scalar mass density, but these profiles
have no defensible scalar value. Full source profiles remain authoritative.
With `--cli` the helper checks the executable's registered command contract before
creating a new review folder or running a material mutation. It requires explicit
physical-card support, native inspection and library-v2 export. It uses a fresh
CLI process, no geometry and no existing user design. The actual application
serializes the v2 catalog and its revisions; Python does not approximate serde
canonicalization. Every card, source, condition and density is compared before
and after a separate-process cold reopen/re-export. The extended unsupported
properties remain in this package, not falsely marked as embedded in schema 1.
The helper is an offline authoring/integration tool, not an application dependency.
It never runs `material.assign` or changes a PBR appearance. Passing plan and
refusal tests does not imply that modern-runtime persistence was executed; only
a successful helper receipt establishes that separate result. Baseline read-only
native admission and complete library export are deliberately distinct gates.
+133
View File
@@ -0,0 +1,133 @@
# Sourced physical material references
Expanded data checkpoint: **2026-10-22**. Physical engineering data is maintained
separately from the authored render appearance. The original source files below
are authoritative; exported native cards are reproducible projections, not a
second independently edited dataset.
## Delivered coverage
| Collection | Current coverage |
| --- | --- |
| Named reference profiles | **59**, with **416 numerical records** covering **46 quantity types** |
| Public primary source records | **50**, with locators, retrieval date and review limitations |
| Material concept specifications | **600/600** have an explicit physical role, identification requirements and unknowns |
| Concepts with reference candidates | **306**; these can be substrate, constituent, comparison or named-grade candidates |
| Concepts still requiring a specific formulation | **294**; no unrelated numerical material is substituted |
| Authored PBR surface associations | **48/48** linked to reviewed reference candidates and explicit physical-role rules |
| Schema-1 native projections | **59 envelopes**: **48 nonempty**, **11 extended-only**, **113 native property blocks** |
| References eligible for material creation | **46** have a supported point density; other references do not acquire an invented mass density |
A surface reference association is **not identification of a manufactured part**.
A metallic-looking coating is not a metal substrate; a fibre is not a laminate;
feedstock is not an isotropic printed part; loose granular density is not grain
density. References remain explicitly named and require grade, processing and
conditions to match the intended application. No physical material is silently
assigned to a body, and no appearance edit changes its engineering data.
## Data and relationships
- [profiles.json](profiles.json): the 59 exact material identities, numerical
values/ranges, original units, SI conversion, measurement conditions and limits.
- [sources.json](sources.json): primary-source URLs, selected locators and review
records. Public availability is not a redistribution licence or authentication.
- [quantities.json](quantities.json): units, dimensions and bounds. Hardness scales,
thermal transitions, frequencies and directional measurements remain distinct.
- [bindings.json](bindings.json): all 600 concept specifications and 48 authored
surface relationships, including substrate/stack/recipe dependencies.
- [exports/native-cards.json](exports/native-cards.json): conservative native
schema-1 projections, with every excluded property identified and retained in
the extended profiles. This is not itself a native library import file.
- [exports/coverage.json](exports/coverage.json): generated coverage and exclusions.
The references cover metals and solders, engineering polymers, silicones and
resins, additive-manufacturing feedstock and specimens, woods, cork/MDF, glass and
ceramics, PCB and composite constituents, coatings and terrestrial lunar
simulants. New named references include PMMA sheet, PVC stock, Kevlar 49 fibre,
cured epoxy, UV epoxy and specific alkyd, stoving-enamel and acrylic coatings.
## Properties preserved
Mechanical records include density, Young/shear/Poisson measurements, tensile,
yield, compression, shear, flexural properties, elongation, fracture/tear data
and explicitly identified hardness scales where published. Thermal records
include conductivity, expansion, heat capacity, glass transition, melting,
decomposition and qualified service-temperature statements. Electrical records
retain frequency-specific permittivity/loss, resistivity and dielectric strength.
Optical, moisture, granular and application-thickness records retain their own
units and meaning; they are not reinterpreted as native elastic constants.
Each numerical record preserves a source, original value/unit, conversion,
qualifier, condition fields and limitations. A range is not reduced to its
midpoint; a lower-bound strength is not a typical value. Missing conditions and
uncertainties remain null/unknown. Per-property cure schedules survive native
projection. An isotropic elastic block refuses mismatched source, temperature,
moisture, rate, state or direction rather than combining unrelated constants.
Some new coating profiles contain only published temperature or film-thickness
information. Their cured density, modulus and strength remain unknown. In
particular, liquid/mixed-resin density and package weight are not cured-film
material properties. See [source review](SOURCE_REVIEW.md).
## Validate and inspect
All tools use Python 3.11+ standard-library code. From the repository root:
```sh
python -B assets/render/physical/tools/physical.py validate
python -B -m unittest discover -s assets/render/physical/tools -p 'test_*.py'
python -B assets/render/physical/tools/check_physical.py --cli /trusted/solidcraft-cli
python -B assets/render/physical/tools/physical.py resolve SURF-001
python -B assets/render/physical/tools/physical.py prepare steel-c45 --acknowledge-reference
```
The native check calls the read-only `material.properties.validate` command. It
checks numerical admission and property preservation, **not** source authenticity,
material assignment, solver correctness, persistence or library import. Evidence
and executable hashes stay in ignored `target/physical-material-review/`.
## Native library export without data loss
Generate a non-executing plan for all point-density references:
```sh
python -B assets/render/physical/tools/export_library.py --acknowledge-reference
```
With a trusted runtime that actually supports physical-card persistence and
library v2, create and verify a separate reference library:
```sh
python -B assets/render/physical/tools/export_library.py \
--acknowledge-reference --cli /trusted/card-capable/solidcraft-cli
```
Optional repeated `--profile ID` arguments restrict the selection. The exporter
first verifies the command contract, then uses a fresh CLI design with no
geometry, writes a native v2 library and design into a new `target/` directory,
checks every card/source/condition/density, asks the native library inspector to
verify content revisions, and cold-reopens/re-exports the design. It does not
manually approximate Rust's canonical revision serialization. Existing files,
live GUI sessions, user models and body assignments are untouched.
**The baseline Pegasus CLI validates cards but cannot persist them in materials;
the exporter therefore refuses it before creating anything.** Unit tests and that
refusal are not a successful end-to-end v2 export. A successful modern-runtime run
writes its own receipt; no such runtime receipt is implied by the authored data.
The complete extended dataset must accompany the narrower native library.
Electrical, hardness, ranges, directional measurements and unsupported quantities
must not be claimed to have been embedded in schema 1 merely because a profile
has a native projection.
## Integration and publication
Use [INTEGRATION.md](INTEGRATION.md) to connect the records to the separate Render
branch. `runtime_assignment` remains false until actual target selection and
application acceptance are performed. No native document migration, material-card
schema expansion, rendering shader, solver, model, appearance texture or optical
preset is changed here. Full workspace CI remains a separate integration gate.
All new curation, validators and tooling are original contributor work under
MIT OR Apache-2.0. Publisher data remains attributed to its sources; supplier
brand/grade names do not imply affiliation or certification. Selected numerical
facts are documented without bundling supplier tables, PDFs, artwork or code.
+140
View File
@@ -0,0 +1,140 @@
# Source review and interpretation limits
Research date: 2026-10-22. Exact URLs and source locators are stored in
[sources.json](sources.json); each numerical record in [profiles.json](profiles.json)
points to one of those references. Selection and normalization are original work.
Supplier publications retain their own rights and are not bundled or relicensed.
## Evidence levels
The profiles distinguish typical values, published intervals, lower/upper bounds
and explicitly calculated values. A manufacturer datasheet is evidence for the
named grade and stated test, not identification of an object from its rendering.
No record claims certified design allowables. A public URL and retrieval date are
traceability metadata, not a signed source archive or proof that the URL can never
change. Verify the relevant current datasheet and certificate before engineering use.
Missing temperature, strain rate, specimen orientation or test method is marked
unknown. Published crosshead speed is retained as speed, not guessed strain rate.
A source's plus/minus notation is preserved without inventing its confidence level.
## Conflicts and exclusions retained for review
| Source or material | Treatment |
| --- | --- |
| Sandmeyer 304/304L | Use the identified main physical/plate tables; do not average different FAQ values. Typical plate strengths are not minimum-grade specifications. |
| Elgiloy Nickel 200 | Keep the cited metric expansion value; a conflicting imperial entry is not used to create a second inconsistent constant. |
| SAC305 solder | A field with resistivity units but a conductivity label is excluded rather than silently reinterpreted. Solid alloy data does not identify flux-bearing paste. |
| PolyLite ABS | Product-page and wiki densities disagree, so density remains unresolved. The suspect modulus unit is not repaired by intuition. The retained XY/Z strengths remain directional. |
| Isola 370HR | The modulus row cites a flexural test; retain it as flexural modulus, not a complete orthotropic elastic tensor. Dielectric-strength unit entries are not silently reconciled. |
| Fused silica | A softening value is not used as a melting point; uncertain or inconsistent auxiliary optical entries were excluded. |
| Amorim TPS | Use the cited aerospace table rather than mix differing values from another brand page. Room-condition reference properties do not prove ablation performance. |
Every exclusion stays explicit in the relevant profile. Reference candidates that
would imply wrong chemistry were removed during association review: HDPE is not a
vinyl-flooring default; a glass profile is not a polymer interlayer; MDF is not an
unspecified insulation core. Wrought 6061 may only be an explicitly labelled
comparison for an unidentified casting, never a cast-alloy strength certificate.
## Materials requiring special treatment
### Wood and engineered panels
USDA clear-wood results identify species, moisture and test orientation. Clear
specimen means are not graded structural-lumber design values. Their bending
modulus is not renamed tensile modulus or combined with an invented Poisson ratio.
The specific-gravity basis is retained. For the cited 12% moisture rows, wet mass
at the stated specimen volume is derived as `SG * 1000 * (1 + 0.12)` kg/m3, with
1000 kg/m3 explicitly identified as the water-reference convention. That formula
is recorded, checked and not extrapolated to another moisture content.
Birch specimen properties are a veneer/constituent reference, not effective plywood
properties. Panel layup, glue, moisture, thickness class and density must be known.
MEDITE PREMIER >12 to 19 mm values are bounded panel specifications; the missing
board density is not invented. Cork's MDFACADE product name is not confused with
medium-density fibreboard.
### Printing and composites
Printed PLA/ABS coupon directions and reported spreads remain distinct. Feedstock
solid density is not an infilled-part envelope density. SLS PA12 is not automatically
a valid MJF material model. TPU density intervals remain intervals.
AS4 fibre data describes a constituent. AS4/8552 unidirectional laminate data
requires the stated fibre volume, cure and orientation. Neither describes a woven
twill part simply because its texture resembles carbon fibre. The woven aramid
surface remains unresolved until a particular fibre/resin/layup is selected.
### Electronics, elastomers and coatings
FR-4 dielectric values retain frequency, and expansion retains axis and thermal
regime. A solder-mask appearance is not a single FR-4 property card for the whole
board. Copper thickness, laminate construction, mask, components and interfaces
need separate treatment.
Dragon Skin 20's stress at 100% extension is not Young modulus. A reference casting
silicone is not automatically qualified for cable insulation, optical lenses or
space seals. Filled potting and IC mould compounds need their actual filler/cure
recipe; unfilled casting epoxy is only a labelled comparison.
AZ-93 thermal emittance and solar absorptance retain their coating context and
thickness condition. They are not visible RGB, material emission strength or a
bulk heat capacity. A coating's nominal application thickness is not an automatic
thickness assignment to a CAD model.
### Lunar-like resources
The four Exolith datasets are terrestrial laboratory analogues. Grain density,
packing density, void ratio, porosity and apparatus-dependent repose angle remain
separate. They do not identify a geological site, vacuum condition, internal
friction law or reduced-gravity mechanical response. Rock concepts are not assigned
loose-soil density merely to increase numeric coverage.
## Review backlog
The 294 concepts without an applicable named reference need a selected recipe,
grade or multi-material construction. The 306 with candidates still require review
of their stated relation and conditions; some references are constituents or
explicit comparisons, not effective-property defaults. No numerical completeness
percentage is derived from these association counts.
Further evidence is especially needed for actual paint/cure stacks, woven aramid
laminates, named glass/polymer optical grades, flexible-cable formulations, filled
thermal interfaces, resin systems, foams, friction materials and aerospace-qualified
processes. Temperature-dependent curves, fatigue, creep, damping, outgassing and
failure envelopes should be added as documented quantities/models, not filled with
uncited family averages.
## Added source review — 2026-10-22
Eight additional primary supplier/manufacturer references add 43 selected records,
bringing the dataset to 59 profiles, 50 source records and 416 numerical records.
All 48 authored surfaces now have an explicit reference-candidate relationship.
This does not make the 600 concepts numerically complete or identify their actual
manufacturing recipe.
PMMA sheet preserves published intervals for stiffness, Poisson ratio, heat
capacity, thermal expansion/conductivity and electrical resistivity; no midpoint
is selected for elasticity. Rigid PVC stock data does not identify plasticized
cable insulation. Kevlar 49 axial fibre data does not establish cured laminate or
meta-aramid paper/honeycomb properties; those mappings remain separate.
Sikafloor 161 HC compressive and flexural data retain the 28-day cure at 23 C;
its Shore D datum retains the 7-day cure. The manufacturer's mixed-liquid density
is not assigned to the cured resin. UV22 retains two different glass-transition
ranges, distinguishing UV-only cure from thermal post-cure. Its refractive index
is not a spectral model or certified optical transmission curve.
C71 Speedline, Stoving Enamel Hammer and Acrythane SC601 remain named coating
candidates with only the published temperature and/or coverage-basis dry-film
thickness recorded. Temperature statements without duration are upper-bound
manufacturer references, not continuous-use allowables. No cured density, modulus,
strength or film mass is inferred from paint package weights, liquid densities,
nominal coverage, visual roughness or colour. Those unknowns remain visible.
The inspected Kuhn PVC page contains blank mechanical cells and a hardness row
with inconsistent units. Neither is imported. Public reference metadata preserves
the source relationship without claiming that an openly accessible supplier page
has a permissive dataset licence. No PDF, table layout, prose, logo or image from
those publishers is included in this repository.
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,65 @@
{
"schema": "solidcraft-physical-coverage/1",
"profile_count": 59,
"source_count": 50,
"property_records": 416,
"catalog_specifications": 600,
"concepts_with_reference_candidates": 306,
"concepts_needing_specific_recipe": 294,
"surface_specifications": 48,
"surfaces_with_reference_candidates": 48,
"native_property_blocks": 113,
"nonempty_native_cards": 48,
"extended_only_profiles": 11,
"profiles_with_point_density": 46,
"quantities": {
"angle_of_repose": 8,
"compressive_modulus": 2,
"compressive_strength": 14,
"decomposition_temperature": 2,
"density": 49,
"dielectric_strength": 7,
"dry_film_thickness": 4,
"electrical_conductivity_iacs": 3,
"elongation_break": 19,
"flexural_modulus": 14,
"flexural_strength": 16,
"fracture_toughness": 1,
"glass_transition": 11,
"grain_density": 4,
"hardness_brinell": 2,
"hardness_janka": 10,
"hardness_knoop": 2,
"hardness_shore_a": 2,
"hardness_shore_d": 11,
"heat_deflection": 9,
"internal_bond_strength": 1,
"loss_tangent": 9,
"mean_particle_size": 4,
"melting_temperature": 7,
"poisson_ratio": 8,
"porosity": 4,
"refractive_index": 6,
"relative_permittivity": 10,
"service_temperature": 2,
"shear_modulus": 3,
"shear_strength": 10,
"solar_absorptance": 1,
"specific_gravity": 10,
"specific_heat": 16,
"stress_at_strain": 1,
"tear_strength": 1,
"tensile_strength": 24,
"thermal_conductivity": 24,
"thermal_emittance": 1,
"thermal_expansion": 26,
"thickness_swelling": 1,
"void_ratio": 4,
"volume_resistivity": 14,
"water_absorption": 3,
"yield_strength": 10,
"young_modulus": 26
},
"runtime_assignment": false,
"numerically_complete_catalog_claim": false
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+209
View File
@@ -0,0 +1,209 @@
{
"schema": "solidcraft-physical-quantities/1",
"units": {
"density": "kg/m^3",
"young_modulus": "Pa",
"poisson_ratio": "1",
"shear_modulus": "Pa",
"tensile_strength": "Pa",
"yield_strength": "Pa",
"compressive_strength": "Pa",
"flexural_strength": "Pa",
"flexural_modulus": "Pa",
"elongation_break": "1",
"thermal_conductivity": "W/(m K)",
"thermal_expansion": "1/K",
"specific_heat": "J/(kg K)",
"glass_transition": "K",
"melting_temperature": "K",
"heat_deflection": "K",
"volume_resistivity": "ohm m",
"relative_permittivity": "1",
"loss_tangent": "1",
"dielectric_strength": "V/m",
"water_absorption": "1",
"hardness_shore_a": "1",
"hardness_shore_d": "1",
"hardness_knoop": "1",
"hardness_janka": "N",
"service_temperature": "K",
"specific_gravity": "1",
"stress_at_strain": "Pa",
"tear_strength": "N/m",
"refractive_index": "1",
"decomposition_temperature": "K",
"compressive_modulus": "Pa",
"electrical_conductivity_iacs": "1",
"fracture_toughness": "Pa sqrt(m)",
"shear_strength": "Pa",
"hardness_brinell": "1",
"grain_density": "kg/m^3",
"porosity": "1",
"void_ratio": "1",
"mean_particle_size": "m",
"angle_of_repose": "rad",
"thermal_emittance": "1",
"solar_absorptance": "1",
"dry_film_thickness": "m",
"internal_bond_strength": "Pa",
"thickness_swelling": "1"
},
"conversions": {
"kg/m^3": {
"scale": 1.0,
"offset": 0.0
},
"g/cm^3": {
"scale": 1000.0,
"offset": 0.0
},
"Pa": {
"scale": 1.0,
"offset": 0.0
},
"kPa": {
"scale": 1000.0,
"offset": 0.0
},
"MPa": {
"scale": 1000000.0,
"offset": 0.0
},
"GPa": {
"scale": 1000000000.0,
"offset": 0.0
},
"psi": {
"scale": 6894.757293168,
"offset": 0.0
},
"ksi": {
"scale": 6894757.293168,
"offset": 0.0
},
"1": {
"scale": 1.0,
"offset": 0.0
},
"%": {
"scale": 0.01,
"offset": 0.0
},
"W/(m K)": {
"scale": 1.0,
"offset": 0.0
},
"1/K": {
"scale": 1.0,
"offset": 0.0
},
"ppm/K": {
"scale": 1e-06,
"offset": 0.0
},
"1/degF": {
"scale": 1.8,
"offset": 0.0
},
"J/(kg K)": {
"scale": 1.0,
"offset": 0.0
},
"J/(g K)": {
"scale": 1000.0,
"offset": 0.0
},
"K": {
"scale": 1.0,
"offset": 0.0
},
"degC": {
"scale": 1.0,
"offset": 273.15
},
"ohm m": {
"scale": 1.0,
"offset": 0.0
},
"ohm cm": {
"scale": 0.01,
"offset": 0.0
},
"microohm cm": {
"scale": 1e-08,
"offset": 0.0
},
"ohm mm^2/m": {
"scale": 1e-06,
"offset": 0.0
},
"V/m": {
"scale": 1.0,
"offset": 0.0
},
"kV/mm": {
"scale": 1000000.0,
"offset": 0.0
},
"V/mil": {
"scale": 39370.07874015748,
"offset": 0.0
},
"N": {
"scale": 1.0,
"offset": 0.0
},
"N/m": {
"scale": 1.0,
"offset": 0.0
},
"pli": {
"scale": 175.1268352465,
"offset": 0.0
},
"lb/in^3": {
"scale": 27679.904710203,
"offset": 0.0
},
"Btu/(h ft degF)": {
"scale": 1.730734666371,
"offset": 0.0
},
"Btu/(lb degF)": {
"scale": 4186.8,
"offset": 0.0
},
"MPa sqrt(m)": {
"scale": 1000000.0,
"offset": 0.0
},
"Pa sqrt(m)": {
"scale": 1.0,
"offset": 0.0
},
"degF": {
"scale": 0.5555555555555556,
"offset": 255.3722222222222
},
"micrometre": {
"scale": 1e-06,
"offset": 0.0
},
"mil": {
"scale": 2.54e-05,
"offset": 0.0
},
"degree": {
"scale": 0.017453292519943295,
"offset": 0.0
},
"rad": {
"scale": 1.0,
"offset": 0.0
},
"m": {
"scale": 1.0,
"offset": 0.0
}
}
}
+505
View File
@@ -0,0 +1,505 @@
{
"schema": "solidcraft-physical-sources/1",
"sources": [
{
"id": "sandmeyer-304",
"title": "Sandmeyer Steel Alloy 304/304L plate",
"url": "https://www.sandmeyersteel.com/alloy-304-304l/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Main Physical Properties and Typical Mechanical Properties (.375 in plate), not FAQ",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "alfa-6061",
"title": "Alfa Chemistry aluminium alloy 6061",
"url": "https://alloys.alfa-chemistry.com/aluminum-alloy-6061.html",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical properties and T6 sheet (0.020-0.500 in) typical properties",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "metalcor-ti5",
"title": "Metalcor titanium grade 5 3.7164/65",
"url": "https://www.metalcor.de/en/datenblatt/125/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Mechanical and Physical Properties at 20 C",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "cda-c11000",
"title": "Copper Development Association C11000",
"url": "https://alloys.copper.org/alloy/C11000",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical properties table; property-specific temperatures",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "cda-c36000",
"title": "Copper Development Association C36000",
"url": "https://alloys.copper.org/alloy/C36000",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical properties table; property-specific temperatures",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "cda-c93200",
"title": "Copper Development Association C93200",
"url": "https://alloys.copper.org/alloy/C93200",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical properties table; property-specific temperatures",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "elgiloy-ni200",
"title": "Elgiloy Nickel 200 and 201 strip",
"url": "https://www.elgiloy.com/strip-nickel-200-and-201-alloys",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical properties; room-temperature modulus table",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "feinhuette-sac305",
"title": "Feinhuette SAC305 electronic solder",
"url": "https://www.feinhuette.de/en/metals/sn965ag3cu05",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical Data",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-abs-stock",
"title": "Ensinger TECARAN ABS grey",
"url": "https://www.ensingerplastics.com/en/shapes/tecaran-abs-grey",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Facts and Technical details; exact grade/stock-shape values",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-pom-c",
"title": "Ensinger TECAFORM AH natural POM-C",
"url": "https://www.ensingerplastics.com/en-gb/shapes/acetal-tecaform-ah-natural",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Facts and Technical details; exact grade/stock-shape values",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-peek",
"title": "Ensinger TECAPEEK natural PEEK",
"url": "https://www.ensingerplastics.com/en/shapes/peek-tecapeek-natural",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Facts and Technical details; exact grade/stock-shape values",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-pc",
"title": "Ensinger TECANAT natural PC",
"url": "https://www.ensingerplastics.com/en-gb/shapes/polycarbonate-tecanat-natural",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Facts and Technical details; exact grade/stock-shape values",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-pa6-stock",
"title": "Ensinger TECAMID 6 natural PA6",
"url": "https://www.ensingerplastics.com/en/shapes/pa6-tecamid-6-natural",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Facts and Technical details; exact grade/stock-shape values",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-ptfe",
"title": "Ensinger TECAFLON PTFE natural",
"url": "https://www.ensingerplastics.com/en/shapes/tecaflon-ptfe-natural",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Facts and Technical details",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "goodfellow-kapton",
"title": "Goodfellow Kapton HN PI coil",
"url": "https://www.goodfellow.com/global/kapton-hn-pi-coil-group",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Technical properties of supplied coil",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ovako-c45",
"title": "Ovako C45 steel navigator",
"url": "https://steelnavigator.ovako.com/steel-grades/c45/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Other properties; general grade, not an unspecified heat-treatment strength",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-pp",
"title": "Ensinger TECAFINE PP grey",
"url": "https://www.ensingerplastics.com/en-sg/shapes/polypropylene-tecafine-pp-grey",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Technical details for stock shape",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "ensinger-hdpe",
"title": "Ensinger TECAFINE PE 300 black",
"url": "https://www.ensingerplastics.com/en-gb/shapes/polyethylene-tecafine-pe-300-black",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Technical details for stock shape",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "schott-borofloat",
"title": "SCHOTT BOROFLOAT 33 technical details",
"url": "https://www.schott.com/en-us/products/borofloat-p1000314/technical-details",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical, thermal, mechanical and optical tables",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "crystran-silica",
"title": "Crystran fused silica SiO2",
"url": "https://www.crystran.com/optical-materials/fused-silica-sio2/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Optical material data table; inconsistent molecular/softening labels not imported",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "crystran-sapphire",
"title": "Crystran sapphire Al2O3",
"url": "https://www.crystran.com/optical-materials/sapphire-al2o3/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical and optical constants; crystal orientation explicit",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "coorstek-ad96",
"title": "CoorsTek alumina AD-96",
"url": "https://www2.coorstek.com/en/materials/alumina/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "AD-96 column, nominal material-property comparison",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "smoothon-ds20",
"title": "Smooth-On Dragon Skin 20",
"url": "https://www.smooth-on.com/products/dragon-skin-20/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Technical data; 100% modulus is a finite-strain stress, not Young modulus",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "smoothon-ep690",
"title": "Smooth-On EpoxAcast 690",
"url": "https://www.smooth-on.com/products/epoxacast-690/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Cured casting technical properties",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "polymaker-pla",
"title": "Polymaker PolyLite PLA technical sheet",
"url": "https://wiki.polymaker.com/polymaker-products/more-about-our-products/documents/technical-data-sheets/pla/polylite-tm-pla",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Thermal and XY/Z mechanical properties of specified printed test specimens",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "polymaker-abs",
"title": "Polymaker PolyLite ABS product specification",
"url": "https://shop.polymaker.com/products/polylite-abs/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Mechanical XY/Z and thermal table; conflicting density/modulus excluded",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "polymaker-abs-wiki",
"title": "Polymaker PolyLite ABS wiki sheet",
"url": "https://wiki.polymaker.com/polymaker-products/more-about-our-products/documents/technical-data-sheets/abs-asa/polylite-tm-abs",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Density discrepancy, retained for resolution rather than averaged",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "polymaker-tpu",
"title": "Polymaker PolyFlex TPU95 technical sheet",
"url": "https://wiki.polymaker.com/polymaker-products/more-about-our-products/documents/technical-data-sheets/tpu/polyflex-tm-tpu95",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Material density range and nominal Shore hardness",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "eos-pa2200",
"title": "EOS PA 2200 PA12 reference",
"url": "https://www.eos.info/polymer-solutions/polymer-materials/biocompatible",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "PA 2200 typical part properties; not the different CarbonReduced record",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "isola-370hr",
"title": "Isola 370HR laminate and prepreg",
"url": "https://www.isola-group.com/pcb-laminates-prepreg/370hr-laminate-prepreg/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Typical Values, revision E (2024-06); not construction-specific Dk tables",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "hexcel-as4",
"title": "Hexcel HexTow AS4 continuous fibre and laminate data",
"url": "https://www.hexcel.com/Products/Carbon-Fiber/HexTow-Continuous-Carbon-Fiber/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "AS4 12K fibre and AS4/8552 unidirectional laminate columns",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "usda-wood-2021",
"title": "USDA Wood Handbook 2021 Chapter 5",
"url": "https://research.fs.usda.gov/download/treesearch/62244.pdf",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Table 5-3a, printed pages 5-4, 5-6 and 5-7; 12% moisture clear wood",
"license": "US federal government publication; source notice should still be reviewed",
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "amorim-md-facade",
"title": "Amorim MDFACADE decorative cork board",
"url": "https://www.amorimcorkinsulation.com/en/applications/Decorative-board-Cork-at-sight/162/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Technical Features, additive-free decorative expanded cork",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "amorim-tps",
"title": "Amorim aerospace TPS P45/P50",
"url": "https://amorimcorksolutions.com/en-us/materials-applications/aerospace/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Metric TPS materials table; this page is distinct from legacy brand-page values",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "std-polyester-powder",
"title": "Standard Group MT-D1036 polyester powder",
"url": "https://www.std-coatings.com/Powder/564.html",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Powder properties, colour-dependent specific-gravity range; not an Interpon product",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "az93",
"title": "AZ Technology AZ-93 inorganic coating",
"url": "https://www.aztechnology.com/product/1/az-93",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Cured AZ-93 specifications; radiative properties are not visible RGB or volumetric emission",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "az-primer",
"title": "AZ Technology MLP-300-AZ primer",
"url": "https://www.aztechnology.com/product/29/mlp-300-az",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Cured primer specifications",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "medite-premier",
"title": "MEDITE PREMIER datasheet prem-ds-en-13",
"url": "https://medite-smartply.files.svdcdn.com/production/files/MEDITE-PREMIER-Datasheet-prem-ds-en-13.pdf",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "PDF page 5, Technical Specification Sheet; >12 to 19 mm column",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "exolith-lms-1",
"title": "Exolith Lab LMS-1 mare simulant",
"url": "https://exolithsimulants.com/collections/regolith-simulants/products/lms-1-lunar-mare-simulant",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical Properties; terrestrial simulant reference, not an Apollo sample",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "exolith-lhs-1",
"title": "Exolith Lab LHS-1 highlands simulant",
"url": "https://exolithsimulants.com/collections/regolith-simulants/products/lhs-1-lunar-highlands-simulant",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical Properties; terrestrial simulant reference, not an Apollo sample",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "exolith-lms-1d",
"title": "Exolith Lab LMS-1D mare dust simulant",
"url": "https://exolithsimulants.com/collections/regolith-simulants/products/lms-1d",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical Properties; terrestrial simulant reference, not an Apollo sample",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "exolith-lhs-1d",
"title": "Exolith Lab LHS-1D highlands dust simulant",
"url": "https://exolithsimulants.com/collections/regolith-simulants/products/lhs-1d-lunar-highland-dust-simulant",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical Properties; terrestrial simulant reference, not an Apollo sample",
"license": null,
"review": "Selected numerical facts transcribed with original structure; no supplier document, image, implementation or complete table is redistributed."
},
{
"id": "goodfellow-pmma-sheet",
"title": "Goodfellow PMMA film and sheet reference",
"url": "https://www.goodfellow.com/eu/pmma-film-group",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical, electrical, mechanical and thermal property sections; ranges retain their endpoints.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "goodfellow-kevlar49",
"title": "Goodfellow Kevlar 49 polyaramid fibre",
"url": "https://www.goodfellow.com/global/kevlar-49-polyaramid-fibre-group",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Physical, thermal and mechanical properties of the supplied fibre, not a cured woven laminate.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "kuhn-pvc-stock",
"title": "Kuhn PVC stock-material reference",
"url": "https://www.kuhn-marsberg.de/en/product/PVC/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Density, thermal conductivity and electrical properties; ambiguous hardness-unit and empty cells excluded.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "sika-161-cured",
"title": "Sika Sikafloor 161 HC cured resin reference",
"url": "https://phl.sika.com/en/construction/flooring/industry-wet-areas/epoxy-flooring-system/sikafloor-161-hc.html",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Cured resin hardness at 7 days and strengths at 28 days, +23 C. Mixed-resin density is not cured density.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "masterbond-uv22",
"title": "Master Bond UV22 UV-cured epoxy",
"url": "https://www.masterbond.com/tds/uv22",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Typical properties and cure-specific glass-transition ranges; no numerical outgassing result imported.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "hmg-c71",
"title": "HMG C71 Speedline machinery enamel",
"url": "https://hmgpaint.mybigcommerce.com/c71-speedline/",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Manufacturer product description: temperature resistance up to 170 C, dependent on colour; matt, satin and gloss variants.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "hmg-hammer-stoving",
"title": "HMG Stoving Enamel Hammer TOPSTV",
"url": "https://www.hmgpaint.com/products/all-products/topcoat/1928/stoving-enamel-hammer",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Technical properties: coverage basis at 25 micrometres DFT and temperature resistance 160 C.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
},
{
"id": "hmg-sc601-clear",
"title": "HMG Acrythane SC601 Clearcoat",
"url": "https://hmgpaint.com/products/all-products/topcoat/1942/acrythane-sc601-clearcoat",
"accessed_on": "2026-10-22",
"kind": "public_reference",
"locator": "Two-component acrylic/isocyanate clearcoat; stated coverage basis 25 micrometres DFT. No cured density or modulus stated.",
"license": null,
"review": "Selected numerical facts and explicit source conditions, not a copied supplier table. Publicly indexed manufacturer/supplier data were reviewed; publisher rights are not relicensed. No safety or grade certificate is implied."
}
]
}
@@ -0,0 +1,107 @@
#!/usr/bin/env python3
"""Run physical-data checks and optional read-only native card admission.
The optional --cli must point to a trusted existing SolidCraft CLI executable.
It only runs material.properties.validate in fresh CLI processes; it never runs
material.create, material.assign, opens a user's document or writes a native design.
Evidence stays under ignored target/. This is not aggregate workspace CI.
"""
from __future__ import annotations
import argparse
import copy
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import subprocess
import sys
import time
import physical
def digest_file(path):
h=hashlib.sha256()
with path.open('rb') as f:
for block in iter(lambda:f.read(1024*1024),b''):h.update(block)
return h.hexdigest()
def snapshot():
paths=[p for p in physical.PACK.rglob('*') if p.is_file() and '__pycache__' not in p.parts]
paths += [physical.ROOT/p for p in ('docs/render-materials/materials.json','assets/render/starter/materials/manifest.json',
'crates/doc/src/physical_material.rs','crates/engine/src/cmd/material_properties.rs','ATTRIBUTION.md')]
return {p.relative_to(physical.ROOT).as_posix():digest_file(p) for p in sorted(paths)}
def invoke(cli,card):
payload=json.dumps({'card':card},ensure_ascii=True,separators=(',',':'),allow_nan=False)
physical.require(len(payload)<65536,'native candidate exceeds invocation budget')
result=subprocess.run([str(cli),'exec','material.properties.validate',payload],cwd=physical.ROOT,
capture_output=True,timeout=30)
physical.require(len(result.stdout)+len(result.stderr)<4*1024*1024,'native output exceeds budget')
if result.returncode:return result,None
return result,json.loads(result.stdout,object_pairs_hook=physical.unique,parse_constant=physical.reject_constant)
def native_check(cli,items):
physical.require(cli.is_file(),'trusted native CLI not found')
identity={'name':cli.name,'sha256':digest_file(cli),'rebuilt_by_this_gate':False}
receipts=[]
for i,item in enumerate(items):
result,value=invoke(cli,item['card'])
physical.require(result.returncode==0 and isinstance(value,dict) and value.get('valid') is True,
f"native card admission failed: {item['profile_id']}: {result.stderr.decode(errors='replace')[-500:]}")
report=value.get('validation',{})
expected=sorted(r['data']['kind'] for r in item['card']['properties'])
physical.require(sorted(report.get('property_kinds',[]))==expected,'native property kinds changed or were dropped')
receipts.append({'profile_id':item['profile_id'],'valid':True,'property_kinds':expected,
'empty_projection':not expected,'source_review_required':report.get('source_review_required'),
'missing_condition_fields':report.get('missing_condition_fields'),
'isotropic_moduli':report.get('isotropic_moduli')})
if (i+1)%12==0:print(json.dumps({'native_cards_checked':i+1,'total':len(items)}),flush=True)
template=next(x['card'] for x in items if x['profile_id']=='steel-c45')
bad=[]
x=copy.deepcopy(template);next(r for r in x['properties'] if r['data']['kind']=='elasticity')['data']['model']['poisson_ratio']=.5;bad.append(('unstable-isotropic',x))
x=copy.deepcopy(template);x['properties'][0]['source_id']='missing';bad.append(('unknown-source',x))
x=copy.deepcopy(template);x['properties'].append(copy.deepcopy(x['properties'][0]));bad.append(('duplicate-property',x))
x=copy.deepcopy(template);next(r for r in x['properties'] if r['data']['kind']=='density')['data']['kg_m3']=-1;bad.append(('negative-density',x))
refused=[]
for label,card in bad:
result,value=invoke(cli,card)
physical.require(result.returncode!=0 or isinstance(value,dict) and value.get('valid') is False,
f'hostile card was accepted: {label}')
refused.append(label)
return {'binary':identity,'admitted_card_envelopes':len(receipts),
'nonempty_cards':sum(not r['empty_projection'] for r in receipts),
'empty_cards_not_ready':sum(r['empty_projection'] for r in receipts),
'hostile_cases_refused':refused,'receipts':receipts,'persistence_tested':False,'body_assignment_tested':False}
def main():
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--cli',type=Path)
args=parser.parse_args();stamp=datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S.%fZ')
folder=physical.ROOT/'target/physical-material-review'/stamp;folder.mkdir(parents=True,exist_ok=False)
start=time.monotonic();before=snapshot();report={'schema':'solidcraft-physical-check/1','status':'fail','source_inputs':before,
'python':sys.version.split()[0],'full_workspace_ci':'not_run_by_this_gate','runtime_assignment':False}
exit_code=1
try:
result=subprocess.run([sys.executable,'-B','-m','unittest','discover','-s','assets/render/physical/tools','-p','test_*.py'],
cwd=physical.ROOT,capture_output=True,timeout=120)
(folder/'python-tests.stdout.txt').write_bytes(result.stdout);(folder/'python-tests.stderr.txt').write_bytes(result.stderr)
physical.require(result.returncode==0,'physical Python regressions failed')
report['python_tests']=result.stderr.decode(errors='replace')[-1000:]
report['coverage']=physical.validate()
bundle=physical.read(physical.PACK/'exports/native-cards.json')
report['native']=native_check(args.cli,bundle['items']) if args.cli else {'status':'not_requested'}
after=snapshot();physical.require(before==after,'physical/source inputs changed during validation')
report['source_inputs_unchanged']=True;report['status']='pass';exit_code=0
except (OSError,ValueError,TypeError,KeyError,subprocess.SubprocessError) as error:
report['error']=str(error)
report['seconds']=round(time.monotonic()-start,3)
(folder/'report.json').write_bytes(physical.encoded(report))
print(json.dumps({'status':report['status'],'report':str((folder/'report.json').relative_to(physical.ROOT)),
'native_requested':bool(args.cli),'runtime_assignment':False,'error':report.get('error')},indent=2))
return exit_code
if __name__=='__main__':raise SystemExit(main())
@@ -0,0 +1,178 @@
#!/usr/bin/env python3
"""Export chosen reference materials through a trusted card-capable SolidCraft CLI.
A fresh command-line session creates no geometry or body assignments. The native
application owns library-v2 serialization and content revisions. All output stays
in a new review directory under target/. Older scalar-only executables refuse
before any material is created. Public reference data is not design certification.
"""
from __future__ import annotations
import argparse
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import subprocess
import sys
import tempfile
import physical as p
MAX_OUTPUT = 16 * 1024 * 1024
PACK_ID = "tangency.physical-references"
SOURCE_FILES = ("profiles.json", "sources.json", "bindings.json", "quantities.json",
"exports/native-cards.json", "tools/physical.py", "tools/export_library.py")
def input_pins() -> dict[str, str]:
return {name: p.sha(p.PACK/name) for name in SOURCE_FILES}
def selection(ids: list[str] | None, acknowledge: bool) -> dict:
p.require(acknowledge, "Explicit --acknowledge-reference is required")
before = input_pins()
p.validate()
profiles, sources, _ = p.load()
selected, omitted = [], []
names = sorted(profiles) if ids is None else ids
p.require(isinstance(names, list) and 0 < len(names) <= 256, "Select 1..256 profiles")
p.require(all(isinstance(name, str) for name in names) and len(names) == len(set(names)), "Duplicate or invalid selected profile")
for id in names:
p.require(id in profiles, f"Unknown profile: {id}")
native = p.native_card(profiles[id], sources)
density = next((row for row in native['card']['properties'] if row['data']['kind'] == 'density'), None)
if density is None:
omitted.append({'profile_id': id, 'reason': 'No supported point density; no nominal midpoint or unrelated substrate is substituted.'})
continue
selected.append({'profile_id': id, 'material_form': profiles[id]['material_form'],
'command': p.prepare(id, True)['commands'][0],
'extended_property_ids': [row['id'] for row in native['not_projected']]})
p.require(selected, "No selected reference has a supported point density")
p.require(before == input_pins(), "Source revision changed during plan preparation")
return {'schema': 'solidcraft-physical-library-plan/1', 'executed': False,
'reference_selection_required': True, 'assigns_bodies': False,
'id': PACK_ID, 'input_sha256': before, 'materials': selected, 'omitted': omitted,
'preserve_extended_profiles': True,
'required_contract': 'material.create physical_card, material.inspect, solidcraft-library/2'}
def registry_check(registry: object) -> None:
p.require(isinstance(registry, list) and len(registry) <= 4096, "Invalid native command registry")
p.require(all(isinstance(row, dict) and isinstance(row.get('id'), str) for row in registry), "Malformed native command record")
commands = {row['id']: row for row in registry}
p.require(len(commands) == len(registry), "Duplicate command ID")
for name in ('file.new', 'material.create', 'material.inspect', 'library.export', 'library.inspect'):
p.require(name in commands, f"Native command unavailable: {name}")
p.require(all(isinstance(commands[name].get('params'), str) for name in ('material.create', 'library.export')), 'Malformed native parameter documentation')
p.require('physical_card' in commands['material.create'].get('params', ''),
"This executable cannot preserve physical cards; scalar material creation is refused")
p.require('solidcraft-library/2' in commands['library.export'].get('params', ''),
"Native library-v2 export is required; scalar export would lose engineering data")
def invoke(cli: Path, arguments: list[str]) -> object:
# Capture into temporary files, then enforce the JSON byte budget before reading.
with tempfile.TemporaryFile() as out, tempfile.TemporaryFile() as err:
result = subprocess.run([str(cli), *arguments], cwd=p.ROOT, stdout=out, stderr=err, timeout=180)
p.require(out.tell() <= MAX_OUTPUT and err.tell() <= MAX_OUTPUT, "CLI output exceeds the diagnostic budget")
err.seek(0)
p.require(result.returncode == 0, 'Native command failed: ' + err.read(2048).decode(errors='replace'))
out.seek(0)
return json.load(out, object_pairs_hook=p.unique, parse_constant=p.reject_constant)
def verify_catalog(catalog: object, plan: dict) -> None:
p.require(isinstance(catalog, dict) and catalog.get('format') == 'solidcraft-library/2', "Native export did not produce library v2")
p.require(catalog.get('id') == PACK_ID, "Wrong native library identity")
entries = catalog.get('entries')
p.require(isinstance(entries, list) and len(entries) == len(plan['materials']), "Missing or extra native materials")
expected = {row['command']['params']['name']: row['command']['params'] for row in plan['materials']}
found = set()
for entry in entries:
p.require(isinstance(entry, dict), "Invalid native entry")
definition = entry.get('definition', {})
p.require(isinstance(definition, dict) and definition.get('kind') == 'material', "Unexpected appearance entry")
value = definition.get('value', {})
p.require(isinstance(value, dict), "Invalid material definition")
name = value.get('name')
p.require(name in expected and name not in found, "Unknown or duplicate exported material")
found.add(name)
reference = expected[name]
p.require(value.get('density_g_cm3') == reference['density_g_cm3'], "Assigned reference density changed")
p.require(value.get('physical_card') == reference['physical_card'], "A physical card, source or condition was changed or dropped")
p.require(value.get('appearance') is None, "Physical export must not assign a visual appearance")
revision = entry.get('revision', '')
p.require(isinstance(revision, str) and len(revision) == 64 and all(c in '0123456789abcdef' for c in revision), "Invalid native revision")
# Revisions are subsequently verified by the real library.inspect command,
# not a parallel Python approximation of serde's ordering/float formatting.
def library_command(path: Path) -> dict:
return {'command': 'library.export', 'params': {
'path': str(path), 'id': PACK_ID, 'name': 'Physical references - select grade and conditions',
'format': 'solidcraft-library/2', 'overwrite': False,
'provenance': {'license': 'MIT OR Apache-2.0', 'author': 'SolidCraft contributors',
'source_url': 'https://github.com/0xlillith/solidcraft/tree/assets/material-physical-properties/assets/render/physical'}}}
def export(cli: Path, plan: dict) -> dict:
cli = cli.resolve(strict=True)
p.require(cli.is_file(), "Trusted CLI executable not found")
p.require(plan.get("input_sha256") == input_pins(), "Stale physical reference plan")
registry_check(invoke(cli, ['commands']))
# No live app, existing document, source profile, output file or library is touched.
stamp = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S.%fZ')
folder = p.ROOT / 'target/physical-library-exports' / stamp
p.require(not folder.exists(), "Review output already exists")
folder.mkdir(parents=True, exist_ok=False)
output, cold = folder/'physical-reference-library.json', folder/'cold-reopened-library.json'
design = folder/'physical-reference-library-design.json'
script = folder/'create.json'
commands = [{'command': 'file.new', 'params': {'name': 'Physical reference library'}}]
commands += [row['command'] for row in plan['materials']]
commands.append(library_command(output))
script.write_bytes(p.encoded({'commands': commands}))
before = input_pins()
p.require(before == plan['input_sha256'], 'Source changed after native preflight')
invoke(cli, ['run', str(script), '--results', '--save', str(design)])
catalog = p.read(output)
verify_catalog(catalog, plan)
inspected = invoke(cli, ['exec', 'library.inspect', json.dumps({'path': str(output)})])
p.require(isinstance(inspected, dict) and inspected.get('catalog') == catalog, "Native library inspection disagrees with its export")
# New process, cold document load and native re-export must preserve every card.
reopen = folder/'reopen.json'
reopen.write_bytes(p.encoded({'commands': [library_command(cold)]}))
invoke(cli, ['run', str(reopen), '--in', str(design), '--results'])
restored = p.read(cold)
verify_catalog(restored, plan)
p.require(restored == catalog, "Cold reopen changed the native material library")
p.require(before == input_pins(), "Source revision changed during library export")
receipt = {'schema': 'solidcraft-physical-library-export-receipt/1', 'status': 'pass',
'cli_sha256': p.sha(cli), 'input_sha256': before, 'library_sha256': p.sha(output),
'material_count': len(plan['materials']), 'omitted': plan['omitted'],
'native_inspection': True, 'cold_reopen': True, 'body_assignment': False,
'engineering_certification': False, 'extended_data_embedded': False,
'library': str(output.relative_to(p.ROOT))}
(folder/'receipt.json').write_bytes(p.encoded(receipt))
return receipt
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--profile', action='append', help='Exact reference ID; default selects all point-density references')
parser.add_argument('--acknowledge-reference', action='store_true')
parser.add_argument('--cli', type=Path, help='Trusted card-capable executable; absent means a non-executing JSON plan only')
args = parser.parse_args()
try:
plan = selection(args.profile, args.acknowledge_reference)
result = export(args.cli, plan) if args.cli else plan
print(json.dumps(result, indent=2, ensure_ascii=True, allow_nan=False))
return 0
except (OSError, ValueError, KeyError, TypeError, subprocess.SubprocessError) as error:
print(json.dumps({'status': 'fail', 'error': str(error)}), file=sys.stderr)
return 1
if __name__ == '__main__':
raise SystemExit(main())
+348
View File
@@ -0,0 +1,348 @@
#!/usr/bin/env python3
"""Validate physical references and prepare non-executing schema-1 material cards.
JSON masters retain provenance, directional data, limits and unsupported quantities.
No CAD document is opened, no body is assigned and no missing constant is invented.
"""
from __future__ import annotations
import argparse
from collections import Counter
import hashlib
import json
import math
from pathlib import Path
import re
import sys
ROOT = Path(__file__).resolve().parents[4]
PACK = ROOT / 'assets/render/physical'
MAX_BYTES = 8 * 1024 * 1024
ID = re.compile(r'[a-z][a-z0-9-]{0,63}\Z')
CORE = {'density','elasticity','tensile_strength','yield_strength','compressive_strength','shear_strength','thermal_conductivity','thermal_expansion','specific_heat'}
PRESSURE = {'young_modulus','shear_modulus','tensile_strength','yield_strength','compressive_strength','shear_strength','flexural_strength','flexural_modulus','compressive_modulus','stress_at_strain','internal_bond_strength'}
TEMPERATURE = {'glass_transition','melting_temperature','heat_deflection','service_temperature','decomposition_temperature'}
DIMENSIONLESS = {'poisson_ratio','elongation_break','relative_permittivity','loss_tangent','water_absorption','hardness_shore_a','hardness_shore_d','hardness_brinell','hardness_knoop','specific_gravity','electrical_conductivity_iacs','refractive_index','porosity','void_ratio','thermal_emittance','solar_absorptance','thickness_swelling'}
CONVERSIONS = {
'kg/m^3':('density',1.,0.), 'g/cm^3':('density',1000.,0.), 'lb/in^3':('density',27679.904710203,0.),
'Pa':('pressure',1.,0.), 'kPa':('pressure',1e3,0.), 'MPa':('pressure',1e6,0.), 'GPa':('pressure',1e9,0.),
'psi':('pressure',6894.757293168,0.), 'ksi':('pressure',6894757.293168,0.),
'1':('ratio',1.,0.), '%':('ratio',.01,0.), 'W/(m K)':('conductivity',1.,0.),
'Btu/(h ft degF)':('conductivity',1.730734666371,0.), '1/K':('expansion',1.,0.),
'ppm/K':('expansion',1e-6,0.), '1/degF':('expansion',1.8,0.),
'J/(kg K)':('heat',1.,0.), 'J/(g K)':('heat',1000.,0.), 'Btu/(lb degF)':('heat',4186.8,0.),
'K':('temperature',1.,0.), 'degC':('temperature',1.,273.15), 'degF':('temperature',5/9,255.3722222222222),
'ohm m':('resistivity',1.,0.), 'ohm cm':('resistivity',.01,0.), 'microohm cm':('resistivity',1e-8,0.), 'ohm mm^2/m':('resistivity',1e-6,0.),
'V/m':('field',1.,0.), 'kV/mm':('field',1e6,0.), 'V/mil':('field',1/.0000254,0.),
'N':('force',1.,0.), 'N/m':('tear',1.,0.), 'pli':('tear',175.1268352465,0.),
'Pa sqrt(m)':('toughness',1.,0.), 'MPa sqrt(m)':('toughness',1e6,0.),
'm':('length',1.,0.), 'micrometre':('length',1e-6,0.), 'mil':('length',.0000254,0.),
'rad':('angle',1.,0.), 'degree':('angle',math.pi/180,0.)}
QUANTITY = {q:('pressure','Pa') for q in PRESSURE}
QUANTITY.update({q:('temperature','K') for q in TEMPERATURE})
QUANTITY.update({q:('ratio','1') for q in DIMENSIONLESS})
QUANTITY.update({'density':('density','kg/m^3'),'grain_density':('density','kg/m^3'),
'thermal_conductivity':('conductivity','W/(m K)'), 'thermal_expansion':('expansion','1/K'),
'specific_heat':('heat','J/(kg K)'), 'volume_resistivity':('resistivity','ohm m'),
'dielectric_strength':('field','V/m'), 'hardness_janka':('force','N'), 'tear_strength':('tear','N/m'),
'fracture_toughness':('toughness','Pa sqrt(m)'), 'mean_particle_size':('length','m'),
'dry_film_thickness':('length','m'), 'angle_of_repose':('angle','rad')})
class Invalid(ValueError):
"""A physical reference violates the reviewed contract."""
def require(ok, message):
if not ok: raise Invalid(message)
def unique(pairs):
out = {}
for key, value in pairs:
require(key not in out, f'duplicate JSON field: {key}')
out[key] = value
return out
def reject_constant(value):
raise Invalid(f'non-finite JSON constant: {value}')
def read(path):
require(path.is_file() and not path.is_symlink(), f'expected regular input: {path.name}')
require(path.stat().st_size <= MAX_BYTES, f'oversized input: {path.name}')
raw = path.read_bytes();require(len(raw) <= MAX_BYTES, 'input grew beyond byte budget')
data = json.loads(raw, object_pairs_hook=unique, parse_constant=reject_constant)
require(isinstance(data, dict), 'expected JSON object')
return data
def text(value, label, limit=2048):
require(isinstance(value,str) and 0 < len(value.encode('utf-8')) <= limit and not any(ord(c)<32 for c in value), f'invalid {label}')
def number(value, low=-1e30, high=1e30):
require(type(value) in (int,float) and low <= value <= high and math.isfinite(value), 'invalid bounded number')
return float(value)
def close(a,b):
return math.isclose(a,b,rel_tol=2e-11,abs_tol=1e-14*max(abs(b),1e-20))
def sha(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
def encoded(value):
return (json.dumps(value,ensure_ascii=True,indent=2,allow_nan=False)+'\n').encode()
def check_conditions(c):
base={'temperature_k','temperature_interval_k','moisture_dry_basis_kg_kg','strain_rate_per_s','state','direction','test_method'}
numeric={'crosshead_speed_mm_min','frequency_hz','specimen_thickness_mm','engineering_strain','heating_rate_k_min',
'test_stress_pa','wavelength_nm','minimum_film_thickness_m','immersion_hours','specimen_mass_kg'}
strings={'environment','thermal_regime','density_basis','excitation'}
require(isinstance(c,dict) and base<=set(c)<=base|numeric|strings|{'lower_thickness_exclusive','thickness_range_mm'},'unknown or missing condition field')
for key in numeric & set(c): number(c[key],0,1e18)
for key in strings & set(c): text(c[key],key,256)
if 'lower_thickness_exclusive' in c: require(type(c['lower_thickness_exclusive']) is bool,'thickness endpoint flag must be Boolean')
if 'thickness_range_mm' in c:
bounds=c['thickness_range_mm']
require(isinstance(bounds,list) and len(bounds)==2 and 0<number(bounds[0])<number(bounds[1]),'invalid specimen thickness range')
if c['temperature_k'] is not None: number(c['temperature_k'],1e-20,20000)
def check_property(p, sources):
require(isinstance(p,dict),'property must be an object')
required={'id','quantity','value','unit','qualifier','source_id','source_locator','original','conditions','note','uncertainty'}
require(required <= set(p) <= required|{'derivation'},'unknown or missing property fields')
text(p['id'],'property ID',128)
q=p['quantity'];require(q in QUANTITY and p['unit']==QUANTITY[q][1],'unknown quantity or wrong SI unit')
require(p['source_id'] in sources,'unresolved property source');text(p['source_locator'],'source locator')
require(p['qualifier'] in {'typical','reported_range','lower_bound','upper_bound','derived'},'invalid qualifier')
orig=p['original'];require(isinstance(orig,dict) and set(orig)=={'value','unit','scale_to_si','offset_to_si'},'invalid original quantity')
require(orig['unit'] in CONVERSIONS,'unknown source unit')
dim,scale,offset=CONVERSIONS[orig['unit']]
require(dim==QUANTITY[q][0],'source quantity has incompatible dimensions')
require(number(orig['scale_to_si'])==scale and number(orig['offset_to_si'])==offset,'conversion recipe altered')
value,raw=p['value'],orig['value'];is_range=isinstance(value,list)
require(is_range==isinstance(raw,list),'range/scalar mismatch')
if is_range:
require(len(value)==len(raw)==2 and p['qualifier']=='reported_range','range needs two endpoints and range qualifier')
require(number(value[0])<=number(value[1]),'reversed range')
else: require(p['qualifier']!='reported_range','scalar cannot claim range')
for v,r in zip(value if is_range else [value],raw if is_range else [raw]):
v,r=number(v),number(r);require(close(v,r*scale+offset),'SI value does not reproduce cited original')
if q not in {'thermal_expansion','poisson_ratio'}: require(v>=0,'negative physical value')
if q in TEMPERATURE|{'density','grain_density','young_modulus','shear_modulus','specific_heat','thermal_conductivity','relative_permittivity','refractive_index'}:require(v>0,'positive physical quantity required')
if q in {'porosity','thermal_emittance','solar_absorptance'}:require(v<=1,'fraction above one')
if q.startswith('hardness_shore_'):require(v<=100,'Shore hardness above 100')
c=p['conditions'];check_conditions(c)
for key in ['temperature_k','moisture_dry_basis_kg_kg','strain_rate_per_s']:
require(key in c,'condition field must be explicit even when unknown')
if c[key] is not None:number(c[key],0,1e12)
if c.get('temperature_interval_k') is not None:
interval=c['temperature_interval_k'];require(isinstance(interval,list) and len(interval)==2,'temperature interval shape')
require(0<number(interval[0])<=number(interval[1])<=20000,'temperature interval bounds')
for key in ('state','direction','test_method'):text(c.get(key),key,1024)
if p['uncertainty'] is not None:
u=p['uncertainty'];require(isinstance(u,dict) and set(u)=={'reported_plus_minus','interpretation'},'invalid uncertainty')
number(u['reported_plus_minus'],0);text(u['interpretation'],'uncertainty interpretation')
require((p['qualifier']=='derived')==('derivation' in p),'derived value needs a calculation receipt')
def check_derivation(record, properties):
d=record['derivation'];require(isinstance(d,dict),'invalid derivation')
require(all(key in properties for key in d.get('input_property_ids',[])),'missing derivation input')
if d.get('formula')=='young_modulus/(2*shear_modulus)-1':
require(record['quantity']=='poisson_ratio','wrong modulus-derived quantity')
e,g=properties['young_modulus'],properties['shear_modulus']
require(e['source_id']==g['source_id']==record['source_id'],'mixed-source elastic derivation')
expected=number(e['value'])/(2*number(g['value'],1e-30))-1
require(-1<expected<.5 and close(record['value'],expected),'incorrect derived Poisson ratio')
elif d.get('formula')=='specific_gravity*1000*(1+moisture_dry_basis_kg_kg)':
require(record['quantity']=='density','wrong moisture-derived quantity')
moisture=number(d.get('moisture_dry_basis_kg_kg'),0,100)
require(d.get('water_reference_density_kg_m3')==1000 and record['conditions']['moisture_dry_basis_kg_kg']==moisture,'moisture-density conditions mismatch')
expected=number(properties['specific_gravity']['value'])*1000*(1+moisture)
require(close(record['value'],expected),'incorrect moisture-derived density')
else:raise Invalid('unreviewed derivation formula')
def validate_data(data, source_data, binding, registry, root=ROOT):
require(data.get('schema')=='solidcraft-physical-reference-profiles/1' and data.get('runtime_assignment') is False,'unknown profile schema or false runtime claim')
require(source_data.get('schema')=='solidcraft-physical-sources/1','unknown source schema')
require(binding.get('schema')=='solidcraft-physical-bindings/1','unknown binding schema')
require(registry.get('units')=={q:u for q,(_,u) in QUANTITY.items()},'quantity registry changed without validator review')
require(registry.get('conversions')=={k:{'scale':v[1],'offset':v[2]} for k,v in CONVERSIONS.items()},'unit conversion registry drift')
require(isinstance(source_data.get('sources'),list) and 1<=len(source_data['sources'])<=256,'source count')
sources={}
for s in source_data['sources']:
require(isinstance(s,dict) and isinstance(s.get('id'),str) and ID.fullmatch(s['id']) is not None and s['id'] not in sources,'invalid/duplicate source')
text(s.get('title'),'source title',256);text(s.get('url'),'source URL')
require(s['url'].startswith('https://') and s.get('kind')=='public_reference','source must identify public HTTPS reference')
require(s.get('license') is None or isinstance(s['license'],str),'invalid source rights metadata')
sources[s['id']]=s
profiles={};require(isinstance(data.get('profiles'),list) and 1<=len(data['profiles'])<=256,'profile count')
for p in data['profiles']:
require(isinstance(p,dict) and isinstance(p.get('id'),str) and ID.fullmatch(p['id']) is not None and p['id'] not in profiles,'invalid/duplicate profile')
require(p.get('family') in {'wood','polymer','glass','metal','other'},'unknown native material family')
require(p.get('source_id') in sources and p.get('certified_design_allowables') is False,'invalid provenance or false certification')
require(type(p.get('isotropic_reference_model')) is bool,'isotropic policy must be Boolean')
text(p.get('designation'),'designation',256);text(p.get('reference_state'),'state',800)
allowed={'id','designation','family','material_form','reference_state','identity_status','isotropic_reference_model','source_id','properties','limitations','certified_design_allowables','quarantined_source_ids'}
require(set(p)<=allowed,'unknown profile field')
require(all(x in sources for x in p.get('quarantined_source_ids',[])),'unresolved quarantined source')
require(isinstance(p.get('properties'),list) and len(p['properties'])<=128,'property list bounds')
seen={}
for r in p['properties']:
check_property(r,sources);require(r['id'] not in seen,'duplicate property ID');seen[r['id']]=r
for r in seen.values():
if r['qualifier']=='derived':check_derivation(r,seen)
profiles[p['id']]=p
expected={'catalog':('docs/render-materials/materials.json',600),'surfaces':('assets/render/starter/materials/manifest.json',48)}
authorities={}
for key,(path,count) in expected.items():
spec=binding.get('inputs',{}).get(key,{})
require(spec.get('path')==path and type(spec.get('count')) is int and spec['count']==count,'authority path/count mismatch')
require(spec.get('sha256')==sha(root/path),'catalog changed: explicit binding review required')
authorities[key]=read(root/path)['items'];require(len(authorities[key])==count,'authority count mismatch')
concepts={c['id']:c for c in authorities['catalog']}
require(len(binding.get('catalog',[]))==600 and len(binding.get('surfaces',[]))==48,'all concepts/surfaces need physical specifications')
checked={}
for b in binding['catalog']:
id=b.get('catalog_id');require(id in concepts and id not in checked and b.get('name')==concepts[id]['name'],'invalid/duplicate concept binding')
require(b.get('auto_assign_body') is False and b.get('preserve_existing_material') is True and b.get('unknown_is_zero') is False,'appearance must not overwrite host physics')
require(b.get('physical_role') in binding.get('role_definitions',{}),'unknown physical role')
require(isinstance(b.get('required_definition_fields'),list) and len(b['required_definition_fields'])>0,'physical requirements cannot be empty')
refs=b.get('references');require(isinstance(refs,list) and len(refs)<=16,'reference list bounds');pairs=set()
for ref in refs:
require(ref.get('profile_id') in profiles,'unresolved physical reference');text(ref.get('relation'),'reference relationship',256)
pair=(ref['profile_id'],ref['relation']);require(pair not in pairs,'duplicate reference');pairs.add(pair)
require(b.get('status')==('reference_selection_required' if refs else 'specific_recipe_required'),'status conflicts with references')
checked[id]=b
surface_ids=set();originals={s['id']:s for s in authorities['surfaces']}
for s in binding['surfaces']:
id=s.get('surface_id');require(id in originals and id not in surface_ids,'invalid surface ID');original=originals[id]
require(s.get('catalog_ids')==original['catalog_ids']==s.get('physical_binding_ids'),'surface physical association drift')
require(s.get('name')==original['name'] and s.get('auto_assign_body') is False and s.get('preserve_existing_material') is True,'surface scope/name mismatch')
first=checked[original['catalog_ids'][0]]
require(s.get('status')==first['status'] and s.get('physical_role')==first['physical_role'],'surface status/role drift');surface_ids.add(id)
return profiles,sources
def load():
data,sources,binding,registry=[read(PACK/name) for name in ('profiles.json','sources.json','bindings.json','quantities.json')]
profiles,source_map=validate_data(data,sources,binding,registry)
return profiles,source_map,binding
def native_condition(p,r):
c=r['conditions'];extra={k:v for k,v in c.items() if k not in {'state','temperature_k','moisture_dry_basis_kg_kg','strain_rate_per_s'} and v is not None}
state=c['state']+' | '+r['qualifier']+' reference; source review required; not design allowables.'
if extra:state+=' | '+json.dumps(extra,sort_keys=True,separators=(',',':'))
if r['uncertainty'] is not None:state+=' | Reported uncertainty retained in extended profile.'
if r['qualifier']=='derived':state+=' | Derived value; formula retained in extended profile.'
require(len(state.encode())<=1024,'native condition overflow')
return {k:c[k] for k in ('temperature_k','moisture_dry_basis_kg_kg','strain_rate_per_s')}|{'state':state}
def native_card(p,sources):
selected=[];used=set();properties=p['properties'];by_q={}
for r in properties:by_q.setdefault(r['quantity'],[]).append(r)
def point(q):
rows=by_q.get(q,[])
if len(rows)!=1:return None
r=rows[0]
return r if type(r['value']) in (int,float) and r['qualifier'] in {'typical','derived'} and r['conditions']['direction'] in {'not_reported','isotropic'} else None
def add(r,data,extra=()):
selected.append({'data':data,'source_id':r['source_id'],'conditions':native_condition(p,r)})
used.add(r['id']);used.update(extra)
rho=point('density')
if rho:add(rho,{'kind':'density','kg_m3':rho['value']})
cp=point('specific_heat')
if cp:add(cp,{'kind':'specific_heat','j_kg_k':cp['value']})
for q in ('tensile_strength','yield_strength','compressive_strength','shear_strength'):
r=point(q)
if r:add(r,{'kind':q,'pa':r['value']})
if p['isotropic_reference_model']:
e,nu=point('young_modulus'),point('poisson_ratio')
if e and nu:
keys=('temperature_k','temperature_interval_k','moisture_dry_basis_kg_kg','strain_rate_per_s','state','direction','environment','thermal_regime','specimen_thickness_mm','heating_rate_k_min')
require(e['source_id']==nu['source_id'] and all(e['conditions'].get(k)==nu['conditions'].get(k) for k in keys),'mixed elastic source/conditions')
require(-1<number(nu['value'])<.5,'unstable isotropic Poisson ratio')
add(e,{'kind':'elasticity','model':{'symmetry':'isotropic','young_modulus_pa':e['value'],'poisson_ratio':nu['value']}},[nu['id']])
selected[-1]['conditions']['state']+=' | Isotropic reference model; not validated part behavior.'
if nu['qualifier']=='derived':selected[-1]['conditions']['state']+=' Poisson ratio derived from same-source E and G, not independently measured.'
for q,field in [('thermal_conductivity','w_m_k'),('thermal_expansion','per_k')]:
r=point(q)
if r:
add(r,{'kind':q,field:[r['value']]*3,'axes':'material123'})
selected[-1]['conditions']['state']+=' | Isotropic reference approximation, not measured directional tensor.'
selected.sort(key=lambda r:r['data']['kind']);source_ids=sorted({r['source_id'] for r in selected} or {p['source_id']})
card={'schema_version':1,'designation':p['designation'],'family':p['family'],
'sources':[{'id':id,'title':sources[id]['title'],'kind':'public_reference','license':sources[id]['license'],'reference':sources[id]['url']} for id in source_ids],
'properties':selected}
return {'profile_id':p['id'],'card':card,
'native_projection_status':'partial_native_card' if selected else 'extended_data_only',
'exported_property_ids':sorted(used),
'not_projected':[{'id':r['id'],'quantity':r['quantity'],'reason':'Retained in extended profile: unsupported native quantity, range/bound, directional measurement or incomplete constitutive model.'} for r in properties if r['id'] not in used],
'missing_native_kinds':sorted(CORE-{r['data']['kind'] for r in selected}),
'reference_selection_required':True,'runtime_assignment':False}
def make_exports():
profiles,sources,bindings=load();cards=[native_card(p,sources) for p in profiles.values()]
pin={name:sha(PACK/name) for name in ('sources.json','profiles.json','quantities.json','bindings.json')}
data={'schema':'solidcraft-physical-card-bundle/1','native_card_schema':1,'native_library_format':False,
'source_sha256':pin,'generator_sha256':sha(Path(__file__)),'runtime_assignment':False,'items':cards}
coverage={'schema':'solidcraft-physical-coverage/1','profile_count':len(profiles),'source_count':len(sources),
'property_records':sum(len(p['properties']) for p in profiles.values()),'catalog_specifications':len(bindings['catalog']),
'concepts_with_reference_candidates':sum(bool(c['references']) for c in bindings['catalog']),
'concepts_needing_specific_recipe':sum(not c['references'] for c in bindings['catalog']),
'surface_specifications':len(bindings['surfaces']),
'surfaces_with_reference_candidates':sum(s['status']=='reference_selection_required' for s in bindings['surfaces']),
'native_property_blocks':sum(len(c['card']['properties']) for c in cards),
'nonempty_native_cards':sum(bool(c['card']['properties']) for c in cards),
'extended_only_profiles':sum(not c['card']['properties'] for c in cards),
'profiles_with_point_density':sum(any(r['data']['kind']=='density' for r in c['card']['properties']) for c in cards),
'quantities':dict(sorted(Counter(r['quantity'] for p in profiles.values() for r in p['properties']).items())),
'runtime_assignment':False,'numerically_complete_catalog_claim':False}
return {'native-cards.json':data,'coverage.json':coverage}
def build():
outputs=make_exports();folder=PACK/'exports';folder.mkdir(exist_ok=True)
for name,data in outputs.items():
path=folder/name;require(not path.is_symlink(),'refusing symlink export');path.write_bytes(encoded(data))
return outputs['coverage.json']
def validate():
outputs=make_exports()
for name,data in outputs.items():
path=PACK/'exports'/name
require(path.is_file() and not path.is_symlink() and path.read_bytes()==encoded(data),f'stale export: {name}; review source changes before rebuilding')
return {'status':'pass',**outputs['coverage.json']}
def resolve(id):
profiles,_,binding=load()
if id.startswith('SURF-'):
row=next((s for s in binding['surfaces'] if s['surface_id']==id),None)
require(row is not None,'unknown surface ID');id=row['catalog_ids'][0]
row=next((c for c in binding['catalog'] if c['catalog_id']==id),None)
require(row is not None,'unknown catalog ID')
return {'binding':row,'profiles':[profiles[r['profile_id']] for r in row['references']],'runtime_assignment':False}
def prepare(id,acknowledge):
require(acknowledge,'--acknowledge-reference is required; these are not certified design allowables')
profiles,sources,_=load();require(id in profiles,'unknown reference profile')
p=profiles[id];result=native_card(p,sources)
densities=[v for v in result['card']['properties'] if v['data']['kind']=='density']
require(len(densities)==1,'no single supported density; select a reviewed point value/grade before material creation')
name='REF '+p['designation'];require(len(name.encode())<=128,'material name too long')
return {'schema':'solidcraft-physical-command-plan/1',
'requires':['material.create must explicitly support physical_card; refuse older command contracts',
'review grade, form, temperature, moisture, process and density basis before execution',
'body assignment is a separate explicit operation; preserve existing appearances'],
'executed':False,'assigns_bodies':False,'profile_id':id,
'commands':[{'command':'material.create','params':{'name':name,'density_g_cm3':densities[0]['data']['kg_m3']/1000,'physical_card':result['card']}}],
'extended_profile':'assets/render/physical/profiles.json#'+id,'limitations':p['limitations'],
'omitted_native_properties':result['not_projected']}
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('command',choices=['build','validate','resolve','prepare']);parser.add_argument('id',nargs='?')
parser.add_argument('--acknowledge-reference',action='store_true');args=parser.parse_args()
try:
if args.command=='build':result=build()
elif args.command=='validate':result=validate()
elif args.command=='resolve':result=resolve(args.id or '')
else:result=prepare(args.id or '',args.acknowledge_reference)
print(json.dumps(result,ensure_ascii=True,indent=2,allow_nan=False));return 0
except (ValueError,OSError,TypeError,KeyError,AttributeError,RecursionError) as error:
print(json.dumps({'status':'fail','error':str(error)}),file=sys.stderr);return 1
if __name__=='__main__':
raise SystemExit(main())
@@ -0,0 +1,121 @@
"""Regression checks for explicit, no-loss physical-library export planning."""
import copy
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import export_library as e
import physical as p
def modern_registry():
rows = [{'id': name, 'params': ''} for name in ('file.new', 'material.create', 'material.inspect', 'library.export', 'library.inspect')]
rows[1]['params'] = 'physical_card: schema 1'
rows[3]['params'] = 'format solidcraft-library/2'
return rows
def projected_catalog(plan):
entries = []
for i, row in enumerate(plan['materials']):
params = row['command']['params']
entries.append({'id': e.PACK_ID + '/material-' + str(i), 'revision': 'a'*64,
'definition': {'kind': 'material', 'value': copy.deepcopy(params)}})
return {'format': 'solidcraft-library/2', 'id': e.PACK_ID, 'entries': entries}
class ExportLibraryTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.plan = e.selection(None, True)
def test_plan_covers_every_point_density_without_part_assignment(self):
self.assertEqual(len(self.plan['materials']), 46)
self.assertEqual(len(self.plan['omitted']), 13)
self.assertFalse(self.plan['assigns_bodies'])
self.assertFalse(self.plan['executed'])
self.assertTrue(self.plan['preserve_extended_profiles'])
ids = {x['profile_id'] for x in self.plan['materials']}
omitted = {x['profile_id'] for x in self.plan['omitted']}
self.assertFalse(ids & omitted)
self.assertEqual(len(ids | omitted), 59)
for row in self.plan['materials']:
self.assertEqual(row['command']['command'], 'material.create')
self.assertEqual(set(row['command']['params']), {'name', 'density_g_cm3', 'physical_card'})
def test_acknowledgement_unknown_duplicate_and_empty_selections_refuse(self):
for ids, acknowledge in [(None, False), ([], True), (['unknown'], True), (['steel-c45']*2, True)]:
with self.subTest(ids=ids), self.assertRaises(p.Invalid):
e.selection(ids, acknowledge)
def test_ranges_and_coatings_without_density_are_not_assigned(self):
omitted = {x['profile_id'] for x in self.plan['omitted']}
self.assertTrue({'c71-enamel', 'hmg-stoving-hammer', 'hmg-sc601-clear', 'uv22-coating'} <= omitted)
with self.assertRaises(p.Invalid):
e.selection(['c71-enamel'], True)
plan = e.selection(['pmma-goodfellow-sheet'], True)
params = plan['materials'][0]['command']['params']
self.assertAlmostEqual(params['density_g_cm3'], 1.19)
self.assertNotIn('elasticity', {r['data']['kind'] for r in params['physical_card']['properties']})
def test_old_scalar_registry_refuses_and_modern_contract_is_recognized(self):
e.registry_check(modern_registry())
for field, replacement in [(1, 'density and colour only'), (3, 'scalar library/1')]:
rows = modern_registry()
rows[field]['params'] = replacement
with self.assertRaises(p.Invalid):
e.registry_check(rows)
for value in [None, {}, [None], modern_registry()+[modern_registry()[0]]]:
with self.subTest(value=value), self.assertRaises(p.Invalid):
e.registry_check(value)
def test_catalog_retains_every_native_value_source_and_condition(self):
catalog = projected_catalog(self.plan)
e.verify_catalog(catalog, self.plan)
for mutation in ('drop_card', 'density', 'appearance', 'source', 'state', 'revision', 'duplicate', 'missing'):
value = copy.deepcopy(catalog)
material = value['entries'][0]['definition']['value']
if mutation == 'drop_card':material.pop('physical_card')
elif mutation == 'density':material['density_g_cm3'] *= 2
elif mutation == 'appearance':material['appearance'] = {'name': 'unrequested optical change'}
elif mutation == 'source':material['physical_card']['sources'][0]['reference'] = 'changed'
elif mutation == 'state':material['physical_card']['properties'][0]['conditions']['state'] = 'wrong condition'
elif mutation == 'revision':value['entries'][0]['revision'] = 'not a revision'
elif mutation == 'duplicate':value['entries'][1] = copy.deepcopy(value['entries'][0])
elif mutation == 'missing':value['entries'].pop()
with self.subTest(mutation=mutation), self.assertRaises(p.Invalid):
e.verify_catalog(value, self.plan)
def test_legacy_refusal_does_not_create_output_or_run_mutations(self):
rows = modern_registry()
rows[1]['params'] = 'no card persistence'
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
cli = root/'trusted-cli-fixture'
cli.write_bytes(b'unit test marker; not executable')
with patch.object(p, 'ROOT', root), patch.object(e, 'invoke', return_value=rows) as invoke:
with self.assertRaises(p.Invalid):e.export(cli, self.plan)
invoke.assert_called_once_with(cli.resolve(), ['commands'])
self.assertFalse((root/'target').exists())
def test_stale_plan_refuses_before_executable_invocation(self):
plan = copy.deepcopy(self.plan)
plan['input_sha256']['profiles.json'] = '0'*64
with tempfile.TemporaryDirectory() as temporary:
cli = Path(temporary)/'fixture'
cli.write_bytes(b'fixture')
with patch.object(e, 'invoke') as invoke:
with self.assertRaises(p.Invalid):e.export(cli, plan)
invoke.assert_not_called()
def test_library_command_has_no_overwrite_and_no_body_targets(self):
command = e.library_command(Path('review.json'))
self.assertEqual(command['command'], 'library.export')
self.assertEqual(command['params']['format'], 'solidcraft-library/2')
self.assertIs(command['params']['overwrite'], False)
self.assertNotIn('targets', command['params'])
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,277 @@
"""Regression tests for reference data, physical scope and loss-aware projection."""
import copy
import json
import math
from pathlib import Path
import tempfile
import unittest
import physical as p
class PhysicalTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.data,cls.sources,cls.bindings,cls.registry=[p.read(p.PACK/name) for name in ('profiles.json','sources.json','bindings.json','quantities.json')]
cls.profiles,cls.source_map=p.validate_data(cls.data,cls.sources,cls.bindings,cls.registry)
def mutated(self,which,change):
args=copy.deepcopy([self.data,self.sources,self.bindings,self.registry]);change(args[which])
with self.assertRaises((p.Invalid,KeyError,TypeError)):p.validate_data(*args)
def row(self,id,quantity):
return next(x for x in self.profiles[id]['properties'] if x['quantity']==quantity)
def test_all_profiles_and_associations_validate(self):
self.assertGreaterEqual(len(self.profiles),51)
self.assertEqual(len(self.bindings['catalog']),600)
self.assertEqual(len(self.bindings['surfaces']),48)
def test_duplicate_json_fields_and_nonfinite_literals_refuse(self):
with self.assertRaises(p.Invalid):json.loads('{"density":1,"density":2}',object_pairs_hook=p.unique)
for value in ('NaN','Infinity','-Infinity'):
with self.assertRaises(p.Invalid):json.loads('{"value":'+value+'}',parse_constant=p.reject_constant)
def test_numeric_boolean_text_infinity_and_huge_integers_refuse(self):
for v in (True,False,'1',float('nan'),float('inf'),10**400,-10**400):
with self.subTest(v=repr(v)[:20]),self.assertRaises(p.Invalid):p.number(v)
def test_units_and_source_arithmetic_are_checked(self):
row=copy.deepcopy(self.row('aluminium-6061-t6','young_modulus'))
for change in (lambda x:x.update(unit='MPa'),lambda x:x.update(value=x['value']/1000),
lambda x:x['original'].update(scale_to_si=1),lambda x:x['original'].update(unit='degC')):
bad=copy.deepcopy(row);change(bad)
with self.assertRaises(p.Invalid):p.check_property(bad,self.source_map)
def test_temperature_conversions_are_affine_not_scalar(self):
r=self.row('epoxy-690','heat_deflection')
self.assertAlmostEqual(r['value'],(115-32)*5/9+273.15,places=9)
self.assertIsNone(r['conditions']['temperature_k'])
self.assertAlmostEqual(self.row('pom-c','glass_transition')['value'],213.15)
def test_electrical_resistivity_and_breakdown_have_distinct_units(self):
self.assertAlmostEqual(self.row('steel-304-plate','volume_resistivity')['value'],74e-8)
self.assertAlmostEqual(self.row('ptfe','dielectric_strength')['value'],80e6)
self.assertAlmostEqual(self.row('silicone-ds20','dielectric_strength')['value'],350/.0000254,delta=1e-4)
def test_ranges_never_become_a_midpoint(self):
result=p.native_card(self.profiles['tpu95-feedstock'],self.source_map)
self.assertFalse(any(r['data']['kind']=='density' for r in result['card']['properties']))
with self.assertRaises(p.Invalid):p.prepare('tpu95-feedstock',True)
def test_reversed_range_and_wrong_range_qualifier_refuse(self):
r=copy.deepcopy(self.row('tpu95-feedstock','density'));r['value'].reverse()
with self.assertRaises(p.Invalid):p.check_property(r,self.source_map)
r=copy.deepcopy(self.row('tpu95-feedstock','density'));r['qualifier']='typical'
with self.assertRaises(p.Invalid):p.check_property(r,self.source_map)
def test_limits_are_not_native_nominal_strengths(self):
result=p.native_card(self.profiles['titanium-grade5'],self.source_map)
kinds={r['data']['kind'] for r in result['card']['properties']}
self.assertNotIn('yield_strength',kinds);self.assertNotIn('tensile_strength',kinds)
def test_missing_poisson_ratio_does_not_invent_elasticity(self):
for id in ('steel-304-plate','peek','pa6-stock','epoxy-690'):
result=p.native_card(self.profiles[id],self.source_map)
self.assertNotIn('elasticity',{r['data']['kind'] for r in result['card']['properties']})
def test_native_steel_c45_analytic_elastic_constants(self):
result=p.native_card(self.profiles['steel-c45'],self.source_map)
model=next(r['data']['model'] for r in result['card']['properties'] if r['data']['kind']=='elasticity')
self.assertEqual(model['young_modulus_pa'],210e9)
self.assertAlmostEqual(model['young_modulus_pa']/(2*(1+model['poisson_ratio'])),80.76923076923077e9)
def test_derived_poisson_is_not_presented_as_measured(self):
result=p.native_card(self.profiles['c11000'],self.source_map)
elastic=next(r for r in result['card']['properties'] if r['data']['kind']=='elasticity')
self.assertIn('derived',elastic['conditions']['state'])
self.assertAlmostEqual(elastic['data']['model']['poisson_ratio'],17000/(2*6400)-1)
def test_derived_value_without_receipt_refuses(self):
r=copy.deepcopy(self.row('c11000','poisson_ratio'));del r['derivation']
with self.assertRaises(p.Invalid):p.check_property(r,self.source_map)
def test_wood_moisture_density_not_dry_mass_specific_gravity(self):
r=self.row('wood-white-oak','density')
self.assertAlmostEqual(r['value'],761.6)
self.assertNotEqual(r['value'],680)
self.assertEqual(r['conditions']['moisture_dry_basis_kg_kg'],.12)
def test_wood_flexure_not_isotropic_young_modulus(self):
result=p.native_card(self.profiles['wood-white-oak'],self.source_map)
self.assertNotIn('elasticity',{r['data']['kind'] for r in result['card']['properties']})
self.assertNotIn('compressive_strength',{r['data']['kind'] for r in result['card']['properties']})
self.assertIn('flexural_modulus',{r['quantity'] for r in result['not_projected']})
def test_print_directions_and_uncertainty_are_preserved(self):
profile=self.profiles['pla-printed']
strengths=[x for x in profile['properties'] if x['quantity']=='tensile_strength']
self.assertEqual({x['conditions']['direction'] for x in strengths},{'XY','Z'})
self.assertTrue(all(x['uncertainty'] is not None for x in strengths))
self.assertNotIn('tensile_strength',{r['data']['kind'] for r in p.native_card(profile,self.source_map)['card']['properties']})
def test_finite_strain_stress_never_becomes_elastic_modulus(self):
profile=self.profiles['silicone-ds20']
self.assertEqual(self.row('silicone-ds20','stress_at_strain')['conditions']['engineering_strain'],1)
self.assertNotIn('young_modulus',{r['quantity'] for r in profile['properties']})
def test_source_conflicts_stay_quarantined(self):
profile=self.profiles['abs-printed']
self.assertTrue(profile['quarantined_source_ids'])
self.assertNotIn('density',{r['quantity'] for r in profile['properties']})
with self.assertRaises(p.Invalid):p.prepare('abs-printed',True)
def test_fr4_frequency_and_cte_regimes_stay_distinct(self):
profile=self.profiles['fr4-370hr']
eps=[r for r in profile['properties'] if r['quantity']=='relative_permittivity']
self.assertEqual(len({r['conditions']['frequency_hz'] for r in eps}),5)
z=[r for r in profile['properties'] if r['quantity']=='thermal_expansion' and r['conditions']['direction']=='Z']
self.assertEqual({r['conditions']['thermal_regime'] for r in z},{'pre-Tg','post-Tg'})
self.assertNotIn('elasticity',{r['data']['kind'] for r in p.native_card(profile,self.source_map)['card']['properties']})
def test_silica_and_sapphire_are_not_the_same_material_model(self):
self.assertTrue(self.profiles['fused-silica']['isotropic_reference_model'])
self.assertFalse(self.profiles['sapphire']['isotropic_reference_model'])
self.assertNotIn('thermal_expansion',{r['data']['kind'] for r in p.native_card(self.profiles['sapphire'],self.source_map)['card']['properties']})
def test_references_do_not_relicense_supplier_publications(self):
self.assertIsNone(self.source_map['isola-370hr']['license'])
self.assertIsNone(self.source_map['schott-borofloat']['license'])
def test_simulant_bulk_and_grain_density_remain_distinct(self):
self.assertEqual(self.row('lms-1','density')['value'],1560)
self.assertEqual(self.row('lms-1','grain_density')['value'],2920)
self.assertIn('NOT measured lunar soil',self.profiles['lms-1']['reference_state'])
def test_coating_preserves_substrate_and_has_no_invented_density(self):
result=p.resolve('SURF-045')
self.assertEqual(result['binding']['physical_role'],'layer')
self.assertTrue(result['binding']['preserve_existing_material'])
self.assertFalse(result['binding']['auto_assign_body'])
with self.assertRaises(p.Invalid):p.prepare('az93-coating',True)
def test_no_preparation_without_explicit_reference_acknowledgement(self):
with self.assertRaises(p.Invalid):p.prepare('steel-c45',False)
result=p.prepare('steel-c45',True)
self.assertFalse(result['executed']);self.assertFalse(result['assigns_bodies'])
self.assertEqual(result['commands'][0]['params']['density_g_cm3'],7.8)
self.assertEqual(result['commands'][0]['command'],'material.create')
self.assertNotIn('appearance',result['commands'][0]['params'])
def test_unresolved_ids_fail_not_fallback_to_generic_material(self):
for id in ('MAT-999','SURF-999','__proto__',''):
with self.assertRaises(p.Invalid):p.resolve(id)
def test_false_ready_certification_and_zero_unknown_claims_refuse(self):
self.mutated(0,lambda d:d.update(runtime_assignment=True))
self.mutated(0,lambda d:d['profiles'][0].update(certified_design_allowables=True))
self.mutated(2,lambda d:d['catalog'][0].update(unknown_is_zero=True))
self.mutated(2,lambda d:d['catalog'][0].update(auto_assign_body=True))
def test_duplicate_profiles_sources_properties_refuse(self):
self.mutated(0,lambda d:d['profiles'].append(d['profiles'][0]))
self.mutated(1,lambda d:d['sources'].append(d['sources'][0]))
self.mutated(0,lambda d:d['profiles'][0]['properties'].append(d['profiles'][0]['properties'][0]))
def test_catalog_surface_drift_and_missing_records_refuse(self):
self.mutated(2,lambda d:d['catalog'].pop())
self.mutated(2,lambda d:d['surfaces'].pop())
self.mutated(2,lambda d:d['surfaces'][0].update(physical_binding_ids=['MAT-003']))
self.mutated(2,lambda d:d['inputs']['catalog'].update(sha256='0'*64))
self.mutated(2,lambda d:d['inputs']['catalog'].update(path='../../secret'))
def test_undefined_property_source_and_profile_refuse(self):
self.mutated(0,lambda d:d['profiles'][0]['properties'][0].update(source_id='absent'))
self.mutated(2,lambda d:d['catalog'][0]['references'][0].update(profile_id='absent'))
def test_nonfinite_frequency_and_unknown_conditions_refuse(self):
r=copy.deepcopy(self.row('fr4-370hr','relative_permittivity'))
r['conditions']['frequency_hz']=float('nan')
with self.assertRaises(p.Invalid):p.check_property(r,self.source_map)
r=copy.deepcopy(self.row('fr4-370hr','relative_permittivity'));r['conditions']['temperature']=20
with self.assertRaises(p.Invalid):p.check_property(r,self.source_map)
def test_negative_and_impossible_fraction_refuse(self):
for id,q,value in [('steel-c45','density',-1),('lms-1','porosity',2)]:
r=copy.deepcopy(self.row(id,q));r['value']=value
r['original']={'value':value,'unit':r['unit'],'scale_to_si':1.,'offset_to_si':0.}
with self.assertRaises(p.Invalid):p.check_property(r,self.source_map)
def test_native_projection_has_bounded_unique_kinds(self):
for profile in self.profiles.values():
card=p.native_card(profile,self.source_map)['card']
self.assertLessEqual(len(card['properties']),16)
kinds=[r['data']['kind'] for r in card['properties']]
self.assertEqual(len(kinds),len(set(kinds)))
self.assertTrue(all(len(r['conditions']['state'].encode())<=1024 for r in card['properties']))
def test_authority_bytes_and_exports_are_deterministic(self):
first=p.make_exports();second=p.make_exports()
self.assertEqual(p.encoded(first),p.encoded(second))
self.assertFalse(first['coverage.json']['numerically_complete_catalog_claim'])
def test_bounded_input_and_array_top_level_refuse(self):
with tempfile.TemporaryDirectory() as folder:
path=Path(folder)/'input.json';path.write_text('[]')
with self.assertRaises(p.Invalid):p.read(path)
with path.open('wb') as stream:stream.truncate(p.MAX_BYTES+1)
with self.assertRaises(p.Invalid):p.read(path)
def test_empty_native_projection_is_explicit_not_reported_ready(self):
for id in ('fr4-370hr','az93-coating','mdf-premier-18'):
result=p.native_card(self.profiles[id],self.source_map)
self.assertEqual(result['native_projection_status'],'extended_data_only')
self.assertFalse(result['card']['properties'])
self.assertTrue(result['not_projected'])
def test_wrong_chemistry_is_not_a_convenient_density_fallback(self):
by_id={row['catalog_id']:row for row in self.bindings['catalog']}
for id,wrong in [('MAT-145','hdpe'),('MAT-248','borofloat-33'),('MAT-439','mdf-premier-18')]:
self.assertNotIn(wrong,{r['profile_id'] for r in by_id[id]['references']})
self.assertNotIn('c11000',{r['profile_id'] for r in by_id['MAT-398']['references']})
def test_no_reference_profile_is_duplicated_with_two_roles(self):
for row in self.bindings['catalog']:
ids=[r['profile_id'] for r in row['references']]
self.assertEqual(len(ids),len(set(ids)),row['catalog_id'])
def test_incomplete_coverage_is_not_reported_numerically_complete(self):
c=p.make_exports()['coverage.json']
self.assertEqual(c['nonempty_native_cards']+c['extended_only_profiles'],c['profile_count'])
self.assertEqual(c['concepts_with_reference_candidates']+c['concepts_needing_specific_recipe'],600)
self.assertFalse(c['numerically_complete_catalog_claim'])
self.assertEqual(c['surfaces_with_reference_candidates'],48)
def test_cured_resin_state_survives_native_projection(self):
result=p.native_card(self.profiles['sikafloor161-cured'],self.source_map)
row=next(r for r in result['card']['properties'] if r['data']['kind']=='compressive_strength')
self.assertIn('28 days',row['conditions']['state'])
self.assertEqual(row['conditions']['temperature_k'],296.15)
self.assertNotIn('density',{r['data']['kind'] for r in result['card']['properties']})
def test_distinct_elastic_conditions_cannot_be_mixed(self):
for field,value in [('state','other temper'),('moisture_dry_basis_kg_kg',.1),('strain_rate_per_s',1.0)]:
profile=copy.deepcopy(self.profiles['steel-c45'])
nu=next(r for r in profile['properties'] if r['quantity']=='poisson_ratio')
nu['conditions'][field]=value
with self.subTest(field=field),self.assertRaises(p.Invalid):
p.native_card(profile,self.source_map)
def test_new_reference_ranges_and_fibre_axes_are_not_flattened(self):
pmma=p.native_card(self.profiles['pmma-goodfellow-sheet'],self.source_map)
self.assertNotIn('elasticity',{r['data']['kind'] for r in pmma['card']['properties']})
fibre=p.native_card(self.profiles['kevlar49-fibre'],self.source_map)
self.assertNotIn('elasticity',{r['data']['kind'] for r in fibre['card']['properties']})
self.assertNotIn('tensile_strength',{r['data']['kind'] for r in fibre['card']['properties']})
self.assertNotIn('thermal_expansion',{r['data']['kind'] for r in fibre['card']['properties']})
def test_named_coating_candidates_do_not_invent_bulk_density(self):
for id in ('c71-enamel','hmg-stoving-hammer','hmg-sc601-clear','uv22-coating'):
profile=self.profiles[id]
self.assertNotIn('density',{r['quantity'] for r in profile['properties']})
with self.assertRaises(p.Invalid):p.prepare(id,True)
def test_para_aramid_not_assigned_as_meta_aramid_honeycomb(self):
row=next(r for r in self.bindings['catalog'] if r['catalog_id']=='MAT-482')
self.assertNotIn('kevlar49-fibre',{r['profile_id'] for r in row['references']})
if __name__=='__main__':unittest.main()
+40
View File
@@ -0,0 +1,40 @@
# Raspberry Pi 5 visual reconstruction
The electronics work uses original SolidCraft geometry to replace the source board's generic port and GPIO envelopes with recognizable packages, connectors and contacts. It is a dimension-informed visual model for close inspection and rendering. It is not a complete electrical BOM, netlist, PCB fabrication file or manufacturer-certified enclosure model.
## Reference geometry
The [Raspberry Pi 5 mechanical drawing](https://datasheets.raspberrypi.com/rpi5/raspberry-pi-5-mechanical-drawing.pdf) gives an approximately **85 × 56 mm** board outline, **58 × 49 mm** mounting-hole spacing, **3.5 mm** offsets to the nearby edges and **2.7 mm** mounting-hole diameter. The manufacturer explicitly describes its dimensions as approximate and leaves some components out of the drawing. These are visual reference dimensions, not manufacturing tolerances.
The existing SELENE PCB uses a **1.6 mm** modeled thickness. Its final placement must be read from the evaluated source: the source timeline contains later translations, so the early sketch plane is not the assembled board elevation. The component generator preserves the original mounting position and documents its exact world-coordinate origin.
The [official GPIO documentation](https://www.raspberrypi.com/documentation/computers/raspberry-pi.html#gpio-and-the-40-pin-header) establishes a **2.54 mm** pitch. The visual header therefore uses two rows of 20 individually modeled pins, with a separate dark carrier and visible solder/pad details.
## Component identity and connector inventory
The manufacturer's [Pi 5 introduction](https://www.raspberrypi.com/news/introducing-raspberry-pi-5/) identifies the BCM2712 application processor, LPDDR4X memory, RP1 I/O controller, DA9091 power-management IC, CYW43455 wireless chip and BCM54213 Ethernet PHY. The PHY is visibly rotated 45 degrees. These identities guide named geometry rather than copied chip artwork or logos.
The [current official board documentation](https://www.raspberrypi.com/documentation/computers/sbcs.html#raspberry-pi-5-ports) provides the connector inventory:
| Interface | Visual modeling target |
|---|---|
| USB-A | Two stacked USB 2.0 ports with black tongues, two USB 3.0 ports with blue tongues; hollow metal shells and visible contacts. |
| Ethernet | RJ45 cavity, eight contact representations and two LED windows. |
| GPIO | 40 pins in two rows of 20. |
| Display output | Two micro-HDMI connector shells and contact representations. |
| Power | USB-C shell and tongue. |
| Camera/display | Two 22-contact MIPI FFC connectors at 0.5 mm pitch. |
| PCIe | One 16-contact FFC connector at 0.5 mm pitch. |
| Auxiliary headers | Four-pin fan, three-pin UART, two-pin RTC and four-pin PoE. |
| Storage | Underside microSD cage and contact representations. |
| Board details | Power button, status LED, shields, crystals, inductors, representative passives and plated holes. |
The [manufacturer's PCIe connector specification](https://datasheets.raspberrypi.com/pcie/pcie-connector-standard.pdf) independently confirms the 16-contact, 0.5 mm-pitch interface. Detailed solder terminations, internal contact spring shapes and production-specific component populations remain simplified.
## Rendering and assembly contract
A separate component groups the new electronics and attaches rigidly to the original avionics assembly. Ports have openings, tongues and contacts; separate materials distinguish solder mask, package epoxy, ceramic, connector polymer, shields, heat spreaders, solder and gold-colored plating. Appearance values are illustrative PBR controls. A bulk Brass material on a gold-colored contact is a density proxy, not a claim that the contact is solid gold.
Exact body counts, removed placeholder names, generator hash and native validation results belong in the generated build evidence. No count or completed validation result is inferred from this reference document. Before shipping, check the evaluated component position, mounting-hole and header spacing, representative connector cavities, the rigid attachment and a close-up native render.
All added geometry and documentation are original. No Raspberry Pi trademark artwork, manufacturer CAD, PCB texture or photograph is distributed with the model.
+137
View File
@@ -0,0 +1,137 @@
# SELENE mechanism design and verification contract
The improved native source already contains assembly structure, solar-actuator loops, wheel links and a tour. The functional derivative builds on those mechanisms through SolidCraft commands. This document distinguishes inspected source facts from reconstruction targets and the numeric checks needed to accept the result. It does not certify an unexecuted generator or replace native validation evidence.
## Source assembly
Read-only inspection of the pinned source in [SOURCE.md](SOURCE.md) confirms 49 joints, nine motion links and the 120-step `SELENE tour`. Joint IDs below belong to that source and must be resolved again if a derivative deletes and recreates joints.
| Source mechanism | Native source identity | Relation |
|---|---|---|
| Wheel train | `Wheel axis -1:-90` through `Wheel axis 1:90`, IDs 1–6 | Five links, ratio +1 between neighboring wheel joints. |
| Solar wings | Port/starboard tilt, IDs 9–10 | Mirrored rotation, ratio −1. |
| Solar actuators | Four sets of root pin, stroke and wing pin, IDs 18–29 | Each rod follows a closed loop; slider limits −6 to +0.4 mm. |
| Sensor and dish yaw | Turret ID 7, dish ID 16 | Source demonstrator gear, radii 24:36, ratio −2/3. |
| Moving demonstration track | `Track slide`, ID 48 | Source rack coupling to wheel ID 1, nominal radius 38 mm, reversed. |
| Vial cap | `Vial cap thread`, ID 49 | Cylindrical joint; 1.5 mm axial travel per full turn; slide limits 0–6 mm. |
The source turret elevation is saved at −12 degrees. New bearing geometry and motion links must be constructed in a deliberately restored common neutral pose; mixing that saved pose with zero-angle link offsets can rotate new parts unexpectedly.
## Reconstruction targets
The individual component scripts and the executed derivative are the authority for implementation. The following bounds are deliberately narrower than the source's broad demonstration ranges. They define an inspection envelope to be checked, not a proof of global collision freedom.
| Driver | Intended limits, degrees | Purpose |
|---|---:|---|
| Sensor azimuth | −135 to +135 | Independent pointing with a bounded cable envelope. |
| Sensor elevation | −25 to +45 | Controlled sensor-head tilt. |
| Dish azimuth | −120 to +120 | Independent dish pointing. |
| Dish elevation | −35 to +40 | Controlled dish tilt. |
| Arm azimuth | −45 to +45 | Keep a conservative showcase sweep. |
| Shoulder | −35 to +15 | Restrict travel around the modeled clevis. |
| Elbow | −30 to +55 | Articulate the rebuilt link stack. |
| Wrist and tool tilt | −45 to +45 each | Bound the local tool envelope. |
| Port solar wing | 0 to +30 | Remain within the inherited actuator stroke. |
| Starboard solar wing | −30 to 0 | Mirror the port driver. |
| Gripper jaw driver | −15 to +18 | Move two independently mounted jaws with opposite signs. |
| Suspension arm | −4 to +1.5 | Short-travel closed-loop damper demonstration. |
Remove the source's illustrative sensor-to-dish gear link so either assembly can point without moving the other. Transfer wheel shafts, flanges, hubs and retaining hardware to the parts they physically rotate with. The track remains an optional visualization helper. A 40 mm nominal coupling may match the grouser envelope, but it does not solve discrete grouser contact, slip or rover traction.
The gripper requires separate jaw components, concentric pivot sleeves, opposite revolute motion and contact pads. A revolute joint by itself is insufficient if the jaw is still part of the fixed gripper body. The geometric envelope needs a sweep check independently of the linked angle values.
## Rolling-element bearings
The bearing reconstruction separates the outer race, inner race, cage and each ball. The outer race follows its housing; the inner race follows the shaft. The cage revolves around the shaft axis, and each ball spins relative to the cage while its center orbits with it. Grooved races and spaced cage bridges provide a recognizable section and visible rolling elements.
Let **R** be pitch radius, **r** ball radius, and **θ** the shaft angle relative to the housing. For radial contact and an ideal no-slip bearing with a fixed outer ring:
```text
cage angle relative to housing = 0.5 × (1 − r/R) × θ
ball spin relative to cage = −0.5 × (R/r − r/R) × θ
ball spin relative to housing = −(R/r) × cage angle
```
The sign assumes all revolute axes use the same positive direction. The ball's CAD joint is relative to the cage; substituting an absolute spin rate there produces the wrong motion. These relations follow the cage and rolling-element frequencies in [Schaeffler, An Overview of Bearing Vibration Analysis, section 3](https://www.schaeffler.com/remotemedien/media/_shared_media/08_media_library/01_publications/schaeffler_2/technicalpaper_1/download_1/vibration_analysis_en_en.pdf), specialized to zero contact angle.
The design uses visual clearance between ideal raceway surfaces and balls. It does not solve Hertzian contact, lubrication, cage slip, preload, bearing life or load ratings. The equations drive a kinematic demonstration; no ball-to-race contact force is inferred.
For every bearing, validate a neutral pose, an interior nonzero shaft angle and its return to neutral. Compare both cage angle and relative ball spin with the equations. Also compare a ball's center of mass with its expected orbit; angle labels alone cannot prove that the component frame is correct. The spherical appearance of a ball makes spin visually ambiguous, so the joint readback is part of the evidence.
## Plain yaw supports
[yaw_supports.py](tools/yaw_supports.py) replaces the three source stacks of solid yaw disks with plain journal and thrust geometry. The fixed housing, rotating disk, existing component ownership and joint frames remain in place. Each housing receives a flanged brass bushing; its rotating disk gains an integral hollow steel journal. A stationary retaining pin captures the rotor inside a recess below the unchanged mast or clevis mounting plane.
| Support | Fixed housing / rotor diameter, mm | Steel journal diameter, mm | Brass bore diameter, mm | Retaining pin diameter, mm |
|---|---:|---:|---:|---:|
| Arm | 42 / 36 | 12 | 12.2 | 5 |
| Dish | 20 / 18 | 6 | 6.2 | 2.4 |
| Sensor turret | 28 / 26 | 8 | 8.2 | 3.6 |
The flanged thrust seats are 0.3 mm thick, with nominal seated contact against the rotating disks. Journal radial clearance is 0.1 mm; the retaining pins clear the hollow journals by 0.15 mm radially. Recessed heads provide 0.1 mm axial retention play. Four local reliefs also remove the dish housing's approximately 21.33 mm³ overlap with each original mounting head. This stage adds six bodies and no components or joints. Retainer threads use nominal cylindrical seats; fit classes, preload, friction, wear and structural capacity require separate engineering work.
The official 0.18.2 reduced native fixture reproduced the actual support and neighboring mast/clevis envelopes. Its 101 commands produced 20 bodies and 67 features without timeline errors or warnings. All 44 scoped interference checks, six source-range endpoint drives, nine analytic volume checks and twelve seating/separation measurements passed; each rebuilt journal, bushing and retainer is one connected shell. The largest volume discrepancy was 0.0156%, within the stated 0.02% numerical tolerance. Native section and exterior views were inspected. This fixture evidence does not replace final integration validation; the derivative's narrower pointing limits above remain unchanged.
## Closed-loop checks
### Solar actuators
For each actuator, rotate the tip point **T** about the wing hinge **H** and compute:
```text
stroke(θ) = length(T(θ) − R) − length(T(0) − R)
```
Here **R** is the fixed root pin. The source port hinge is at `(0, −140, 158)`, the root at `(x, −91, 134)`, and the tip at `(x, −180.40678269379407, 169.70686616300375)`, with `x = −62` or `+62`. Positive port tilt rotates about +X. At **20 degrees**, the expected stroke is approximately **−2.8662098186 mm**. The starboard wing has mirrored geometry and a negative drive angle. Verify all four sliders, not only one.
The [SolidCraft mechanism reference](https://github.com/tangency-labs/solidcraft/blob/ac1768e08b83ed33212459951f5b50aa7a57cddc/skills/solidcraft-mechanisms/reference.md) documents this closed-loop pattern, `flip: true` for co-directed frames and comparison with independent length calculations. Do not key the root pins, sliders or wing pins in a study; they are followers.
### Suspension dampers
The short-travel reconstruction gives each drive carrier its own hinge and splits each damper into barrel and rod. With the chosen neutral frame, the root and tip separation is 22 mm. Its analytic stroke relation is:
```text
stroke(θ) = hypot(35 cos θ − 20 sin θ − 35,
−35 sin θ − 20 cos θ − 2) − 22
```
Angles in this formula are radians. Expected strokes are **−2.4462732115 mm at −4 degrees** and **+0.9155981856 mm at +1.5 degrees**. Validate every closed loop against its actual reconstructed world points before using these numbers. Solid coil springs and rigid motor wires do not deform; hide them in moving inspection profiles unless a later animation model replaces them.
The suspension generator retains the original `Damper piston …` and `Suspension bearing …` body labels to avoid costly late rebuilds of the source history. The piston label also names the new rod component; the pivot ring functions as a brass plain bushing and the carrier ring as a fixed spacer. Those retained labels do not alter the geometry or joint behavior.
### Screw cap
The expected axial displacement is `1.5 × angle_degrees / 360` mm. A 360-degree drive gives 1.5 mm; 1,440 degrees gives 6 mm. The angular and axial limits should describe the same four-turn travel. This is an ideal screw coupling; modeled thread surface contact and sealing compression require separate work.
## Native acceptance
Use the model's own saved studies and exact joint names. Save the final derivative with all independent drivers at the agreed neutral pose. A study should return its drivers to neutral so opening another study starts predictably.
The pinned authoring executor is the official v0.18.2 CLI described in [SOURCE.md](SOURCE.md), producing format 28 for compatibility with the personal render branch. Once the master is saved and its bytes are fixed, run the independent validator from the repository root:
```sh
PYTHONDONTWRITEBYTECODE=1 python3 assets/render/selene/tools/validate_native.py \
--cli /path/to/solidcraft-cli \
--model assets/render/selene/models/SELENE-functional.solidcraft \
--out target/selene/validation-final \
--interference-pairs assets/render/selene/motion/interference-pairs.json
```
Use a new output directory for every run. The default suite performs 25 sampled driven cases and a final neutral check. Before each case it reads all independent joint coordinates, resets those that are nonzero, and verifies every primary coordinate is neutral. It compares actual native joint readbacks and world-coordinate centers of mass, retaining requests, responses, hashes, tolerances and failures. It drives an in-memory copy and never saves test poses to the master. The input model and pinned original must have identical hashes before and after validation.
Run full-model native processes serially. The validator confines its native subprocess to two Rayon workers and sets one glibc allocation arena with 64 KiB mmap and trim thresholds. These resource settings are recorded in the report alongside the unchanged geometric tolerances.
Optional `--interference-pairs path/to/pairs.json` accepts a JSON array of exact body-name pairs. The committed [selection](motion/interference-pairs.json) requests seven neutral-pose checks for opposing arm links, the tool wrist, gripper jaws and PCB connector clearances. Each pair receives its own bounded native interference check. The result records whether conservative broad-phase exclusion or exact Boolean intersection resolved it. Missing bodies, skipped or unchecked pairs, truncation and budget exhaustion remain failures or incomplete checks. A request to run the suite is not a passing result; report the returned status and evidence path explicitly.
The minimum evidence for each accepted mechanism is:
- A native timeline with no unreviewed errors or geometry warnings.
- A grounded assembly chain and a clean joint graph; no unexplained floating mechanism, loop conflict or coupling error.
- Actual movement of the intended occurrence, with positions or centers of mass checked before and after.
- Numeric follower agreement at selected interior and endpoint values, with native degrees/mm distinguished from persisted radians/mm.
- Scoped interference checks between parts that must clear each other. Timeouts and unchecked pairs remain incomplete checks, never passes.
- A native view or render showing the actual geometry. A diagram or an inferred body count cannot substitute for this review.
Broad all-body pair checks are expensive and can confuse deliberate mating contact with penetration. Check the relevant opposing link plates, clevis/race seats, shaft/bore and gripper contacts explicitly. A few passing samples do not prove clearance across every possible combination of joint angles.
Lunar gravity, loads, motor torque, thermal behavior, flexible wiring, suspension spring dynamics, granular terrain and autonomous sample capture remain separate simulation and animation work.
+89
View File
@@ -0,0 +1,89 @@
# SELENE functional showcase
This package develops the **author-supplied improved SELENE** into an editable native model for close-up rendering and subsequent mechanism animation. The input is SychPL's `SELENE-showcase.solidcraft`, matched byte for byte to his published file. The original is retained unchanged; the enhanced derivative is [models/SELENE-functional.solidcraft](models/SELENE-functional.solidcraft).
Open the derivative in the personal SolidCraft render branch. The model contains the geometry, component ownership, joints, motion links, physical-material proxies, appearances, browser groups, saved views and inspection studies. Python is only a development tool for reproducing the changes; opening and using the design does not require Python.
![SELENE functional showcase in native Design shading](exports/SELENE-overview.png)
*Actual native Design preview of the neutral master. Close-ups and the product-render executor's current limits are documented in [RENDER.md](RENDER.md).*
## What changed
| Area | Native implementation |
|---|---|
| Rolling-element bearings | Ten open radial bearings at the arm and sensor head. Each has grooved steel races, eight individually represented balls and a brass cage. The inner race follows the shaft, the outer race follows the housing, and linked revolutes drive cage orbit and relative ball spin. |
| Arm joints | Revised plate spacing, bored seats, stepped clamp hubs, shafts, spacers, locking screws and a machined wrist tongue. Rigid decorative hydraulic cylinders that would detach during movement are removed. |
| Gripper | Two separate moving jaw components, concentric brass plain sleeves, opposite revolute motion and elastomer contact pads. Its commanded range is −15° to +18° at the driver. |
| Suspension | Six hinged trailing carriers with reinforcing gussets. Separate hollow damper barrels and rods form six closed revolute–slider–revolute loops. Local support reliefs clear the moving eyes and arms over the bounded travel. |
| Drivetrain | Wheel shafts, hubs, flanges and retaining screws belong to the rotating wheels. Wheel joints attach to the moving carriers. The optional demonstration track uses a 40 mm nominal rolling radius. |
| Yaw supports and pointing | Three solid-disk placeholders become journal supports with flanged brass bushings, hollow rotating journals and recessed axial retaining pins. Sensor and dish pointing are independent; the illustrative gear coupling is removed. |
| Raspberry Pi 5 | A board with rounded corners, mounting holes, plated pads and vias, plus 491 new bodies for packages, shields, passives, headers, connector housings, contacts, solder details and mounting tabs. Hollow ports replace solid envelopes. |
| Materials and appearance | Retained orange/charcoal identity with distinct metals, polymer, elastomer, FR4, silicon and optical surfaces. Opaque bodies carry PBR values; physical density proxies are explicitly separate from optical properties. |
| Sample laboratory | Each glass vial is one hollow native solid with an integral bottom, retaining its original external dimensions. The existing screw-driven cap retains 1.5 mm travel per revolution. The source rock is grounded as demonstration scenery. |
| Inspection | Body groups by function, ten named views with render setup records, seven focused motion studies, and an adapted copy of the author's original tour. |
The native build and independent acceptance run have separate evidence. See [VALIDATION.md](VALIDATION.md) for the final hash, actual counts, executor, completed checks and remaining limitations. [MECHANISMS.md](MECHANISMS.md) gives the joint conventions and independent equations; [ELECTRONICS.md](ELECTRONICS.md) explains the board reconstruction and its fidelity boundaries.
## Using the model
The file opens in the neutral pose: all independent drivers are at zero, including sensor elevation. Saved views are named `SELENE - Whole rover`, `Electronics`, `Shoulder bearing`, `Elbow bearing`, `Gripper`, `Sensor head`, `Sample laboratory`, `Drivetrain`, `Suspension` and `Solar cells`, each with the same `SELENE - ` prefix.
Use the seven inspection studies for drivetrain, solar deployment, arm articulation, gripper, independent sensor pointing, sample cap and suspension. They return their independent drivers to neutral. The inherited tour is named **`SELENE - Legacy author tour (adapted limits)`**; it preserves the author's keyed motion while resolving recreated wheel joints and clamping the selected limits. A keyed study is an authoring resource, not evidence that every frame of every combined pose has been cleared.
For suspension motion, hide the six rigid spring solids and twelve motor leads. They remain useful neutral-pose render details but do not deform. The `motion` visibility profile in [tools/presentation.py](tools/presentation.py) performs this selection and also hides the optional track and source flexible-hose display geometry. Export or save inspection poses to a separate file; keep the master neutral.
The source cap has an ideal screw relation. The six complete vials remain in the sample laboratory assembly. Independent grasp, release, free flight and capture are future work; the current cap motion does not implement those events.
## Reproduce the native derivative
Use Python 3.11+ and the **official SolidCraft v0.18.2 CLI** identified in [SOURCE.md](SOURCE.md). That executor writes `solidcraft/28`, readable by the personal render branch's format-29 reader. The newer official v0.19.0 writes format 30 and is unsuitable as a drop-in authoring replacement for that branch. No format marker is edited to bypass compatibility.
From the repository root:
```sh
PYTHONDONTWRITEBYTECODE=1 python3 assets/render/selene/tools/build.py \
--cli /path/to/solidcraft-cli \
--work target/selene/rebuild \
--output target/selene/SELENE-functional.solidcraft
```
The builder checks the pinned original, evaluates it, then uses only native commands. Stages run in fresh processes so the large model's undo history remains bounded. Each stage retains its command script, full native results, timing trace and saved checkpoint under the selected work directory. Every checkpoint must have a clean feature timeline, unique live body names and a clean joint graph. The build history records hashes of the source, executor, recipes, results and checkpoints.
Resume an interrupted build by repeating the command with `--resume`. The builder rejects changed recipes, executables, results or checkpoints. `--until` stops at a named stage for inspection. Inspect failures in their logs; use a separate work directory when changing an already completed stage.
Run independent acceptance on the saved derivative:
```sh
PYTHONDONTWRITEBYTECODE=1 python3 assets/render/selene/tools/validate_native.py \
--cli /path/to/solidcraft-cli \
--model target/selene/SELENE-functional.solidcraft \
--interference-pairs assets/render/selene/motion/interference-pairs.json \
--out target/selene/acceptance
```
This drives an in-memory copy and compares native joint readbacks and occurrence centers of mass with independent calculations. It never saves test poses over the master. Optional exact-name interference pairs are bounded and report unchecked pairs as incomplete. Detailed execution evidence stays in `target/`; production native files and English documentation are the shipped resources.
## Render preparation and the wider catalog
The full-detail [native 4K studio render](RENDER.md#full-detail-4k-path-traced-studio-image-2026-10-22) now provides a path-traced showcase of the unchanged functional master. It preserves all original visible geometry, uses explicit glass and authored materials, and ships with its exact [request/provenance manifest](scenes/studio-hero.json). The Design previews remain distinct geometry-inspection resources.
The ten saved camera/setup records and the model's body appearances are concrete native resources. [RENDER.md](RENDER.md) documents the actual image workflow and any executor limits. Transparent viewport appearances alone do not establish physical refraction, and density does not supply roughness, friction, electrical behavior or optical transmission.
The existing [48-appearance pack](materials/appearance-pack.json), [33 semantic roles](scenes/roles.json), [ten stage specifications](scenes/stages.json), [12 shots](scenes/shots.json), [UI flow](ui/demo-flow.json) and [future sample dynamics sequence](motion/sample-sequence.json) remain a broader authoring specification. The source is now pinned; those resources still need their individual geometry bindings and renderer admission. The catalog's `runtime_ready: false` status is intentional. No lunar terrain, dust dynamics, calibrated optical library or throw-and-catch simulation is implied by the native model's completion.
```sh
python3 docs/render-materials/validate_catalog.py
python3 assets/render/selene/tools/validate_prepared.py
cargo xtask ci
```
The Python checks validate prepared data and the source contract. They do not replace native geometry, mechanism or visual checks. Repository-wide gate status is recorded separately in the validation report.
## Provenance and scope
[SOURCE.md](SOURCE.md) and [source-lock.json](source-lock.json) pin the exact source bytes and author history. The original rover is credited to 0xlillith, the improved showcase assembly to SychPL, and the emblem to SolidCraft contributors. Ubuntu Light lettering retains its [Ubuntu Font Licence notice](licenses/UFL.txt). The source branch has no separate file-specific license declaration for the showcase additions; that provenance distinction is preserved.
New geometry, procedural scripts and documentation are original contributions under MIT OR Apache-2.0. Exact non-Markdown asset rows are in [ATTRIBUTION.md](../../../ATTRIBUTION.md). Manufacturer CAD, PCB artwork, logos and netlists are not embedded.
The model demonstrates geometric detail and ideal kinematics. It is not a manufacturing release, a complete electrical design, a load-rated bearing selection or a physics simulation. Joint limits are a documented inspection envelope; sampled checks do not certify every simultaneous arm, solar and suspension configuration.
+124
View File
@@ -0,0 +1,124 @@
# SELENE native rendering and reproducible studio showcase
## Full-detail 4K path-traced studio image (2026-10-22)
![SELENE native studio render](exports/SELENE-studio-4k.png)
The selected [3840 x 2400 PNG](exports/SELENE-studio-4k.png) is a completed native SolidCraft GPU-path render of the latest functional master, not a Design screenshot, a generated illustration, a painted geometry replacement or an upscaled preview. The exact request, original resource hashes, editable source and executable identities are retained in [studio-hero.json](scenes/studio-hero.json).
| Property | Verified value |
|---|---|
| Editable authority | `models/SELENE-functional.solidcraft`, original bytes unchanged |
| Functional source commit | `cf8e651022deee528b26c303fde58bf3eb8a22a5` |
| Native model SHA-256 | `3bd2bfa1c6336394c4a4208d30b52c80d06658a3bd7ea57cc93b774411d1bd68` |
| Evaluated native bodies | 2,116; zero timeline errors or warnings |
| Original visible subject | 2,094 bodies; 2,144,099 triangles |
| Separate render-only stage | One passive ground rectangle; 2 triangles |
| Optical bodies | 12 explicitly selected closed dielectric bodies; topology and medium-overlap checks retained |
| Authored surface detail | 48 exact body bindings across six original surface families |
| Output | 3840 x 2400, RGBA8 PNG, sRGB |
| Sampling | 1,024 samples per pixel; maximum path depth 16; seed 21026 |
| Presentation | ACES-fitted tone mapping and edge-aware a-trous denoising |
| Lighting | Original RIG-002 rectangular emitters, explicit 1/16 creative radiance gain, original HDR-002 fill at intensity 0.12 |
| Camera | Perspective fit to all captured body bounding boxes; 8% frame margin |
| Native executable source | `895704a2fdc96dabb9a2afb2e548a8896e4b1e79` |
| Native executable SHA-256 | `3421d538c571647c1f24fd74d63487c4ba4ab597980cfeea111a01c0db94e699` |
| Completed PNG SHA-256 | `40a492e7979137d0f4027300da3d8af7ca1008c1aab2c309259e65d309a17316` |
| PNG length | 2,388,114 bytes; chunk CRCs, bounded scanlines and final-frame hash verified |
The image uses the original saved visibility and neutral pose. There is no mesh simplification, geometry removal, hidden resolution downgrade, engineering-material reassignment or software-device fallback. The native job's logical admission estimate was 2,814,770,732 bytes; this is not a peak-RSS or GPU-memory measurement. Hardware-only policy and Vulkan were enforced on Pegasus; the hardware survey identifies its AMD Radeon 8060S.
### Reproduction
Build the native CLI once, then reuse the loaded model and device:
```sh
cargo build --locked --profile ci -p solidcraft-cli --bin solidcraft-cli
python -i tools/render-library/selene_studio.py --cli /path/to/solidcraft-cli --out /new/output/directory
```
In the resulting interactive studio:
```python
from selene_recipe import hero_request
request = hero_request(studio, width=3840, height=2400, samples=1024)
receipt = studio.render("selene-studio-4k", request, timeout=7200)
studio.close()
```
The helper checks the pinned master and binary, public-command preparation, resource hashes, actual executor, exact requested dimensions and exported frame bytes. The frame is not automatically declared visually accepted. The original camera-fit/publication tools and seven independent Python tests are under `tools/render-library/`; provisional images, native-pixel crops and execution logs stay outside the shipped resource collection.
### Quality and scope
The complete image and four unscaled detail crops were visually reviewed: subject and gripper framing, mechanical edges, electronic connectors, glass and highlights remain readable. Residual Monte Carlo grain is visible at native pixel scale in some vial/lens regions; the current denoiser deliberately does not reconstruct those transmission paths. This is a high-quality native showcase, not proof of zero residual noise or a completed maximum-quality renderer roadmap.
RGB absorption and material recipes are illustrative authored optical choices, not measured glass spectra or engineering properties. The explicit rig gain is a creative lighting adjustment, not an unchanged photometric calibration. Normal maps alter shading, not CAD geometry. LED diffusers are transmitted surfaces, not validated volumetric/mesh emitters. The static image does not establish collision-free motion, contact physics, lunar dust or dynamics.
Native renderer tests passed 457/457. The hardware GPU suite passed 95 tests with three explicitly ignored tests, and the official 12-crate library/web WASM check passed. Whole-workspace type checking, strict lint, resource/persistence command checks and camera/publication tests have their own evidence. Interactive GUI acceptance and whole-project CI are not inferred from this image. The focused Render-workspace harness also passed 58 tests with no failures; its click-stability repair and remaining whole-project acceptance scope are recorded in the batch ledger and PR #7.
## Historical Design previews and earlier CPU-product evidence
The sections below preserve the earlier `10ada1b4` CPU-product/Design observations. Their resource ceilings describe that legacy raster executor, **not** the current native GPU path or the explicit larger CPU raytrace profiles. The whole-master GPU limitation described in that historical workflow is now resolved by the completed image above.
The enhanced native master is the editable authority. Its ten named camera and render-setup records make close inspections reproducible. Body appearances distinguish metals, painted structure, elastomer, FR4, packages, contacts and optical surfaces. The orange and charcoal identity is retained.
## Reusable Design previews
The package ships three previews generated directly by SolidCraft from the final neutral master: a whole-rover overview, the Raspberry Pi assembly and an exposed elbow bearing. They use native Design shading. They are intended for the model's README and showcase preparation; they are not photographs or path-traced optical renders. No raster image generator or painted replacement geometry is involved.
| Preview | Resource | Purpose |
|---|---|---|
| Whole rover | [SELENE-overview.png](exports/SELENE-overview.png) | Overall form, component placement and retained visual identity. |
| Electronics | [SELENE-electronics.png](exports/SELENE-electronics.png) | Board, mounting, packages and connector mouths, viewed from the +X side. |
| Open bearing | [SELENE-bearing.png](exports/SELENE-bearing.png) | Grooved races, individual balls and the brass cage. |
Regenerate them in a fresh output directory:
```sh
PYTHONDONTWRITEBYTECODE=1 python3 assets/render/selene/tools/export_views.py \
--cli /path/to/solidcraft-cli \
--model assets/render/selene/models/SELENE-functional.solidcraft \
--out target/selene/previews
```
The exporter opens the entire native model once, changes only transient body visibility and cameras, and requests three native MCP screenshots. It saves no changes to the master and verifies its SHA-256 afterward. The output report records the actual capture metadata, image hashes, dimensions, cameras and executor hash. Its default size is 1400 × 1000 pixels.
The body selector for electronics retains the PCB, all `Pi5 ` componentry and the `Pi M2.5` mounting hardware. The bearing selector isolates `Bearing Elbow port`. The full overview hides the optional track and source flexible-hose display geometry. None of these visibility changes delete geometry.
## Actual product-render checks
The personal fork at `10ada1b45a4eb285ce752ac72f9aaa968ff8b5c4` was compiled and exercised using its native `cpu-product` executor. The binary reports version 0.18.2 and writes native format 29. Its binary SHA-256 was `2a5485f7c7832d29b6340988837316e247b94eeeb9d4f7333ee616cea38c6826`.
Two bounded opaque PBR images passed native preflight and rendering:
| Actual subject | Visible bodies | Triangles | Admitted job estimate | Output |
|---|---:|---:|---:|---|
| Fresh single arm-bearing fixture generated by `bearings.py` | 20 | 93,654 | 43,594,876 bytes | 768 × 576 PNG, 51,705 bytes |
| Pi isolated from the actual integrated stage-03 model | 504 | 103,004 | 50,848,960 bytes | 768 × 576 PNG, 42,351 bytes |
Both returned no preflight warnings and were visually inspected. The Pi geometry was taken from the integrated rover checkpoint, not a replacement board illustration. The single-bearing image was a reduced fixture of the production geometry; it was not a rendering of the whole rover. Their raw logs, transient native files and review images belong under ignored `target/selene/render-review/`. The final model and reusable Design previews have separate hashes in [VALIDATION.md](VALIDATION.md).
That historical `cpu-product` raster path uses three fixed directional studio lights. Pure metals look dark away from their highlights because it does not provide an ambient or HDRI contribution in this path. This behavior does not mean the material assignments were lost. Use the Design previews to inspect geometry, and inspect PBR behavior with the product executor's actual lighting limits in mind.
## Historical whole-model CPU-product resource limit
At this fork revision the CPU product-image path admits at most **262,144 triangles**, **128 MiB of source geometry**, **384 MiB per job estimate** and **128 million projected pixel tests**. A single arm ball has 10,224 reference-mesh triangles; the 64 arm balls alone account for 654,336 triangles, before the head bearings or rover are included. The full-detail model therefore exceeds this CPU product triangle cap.
Reducing output dimensions reduces image buffers, not reference-mesh triangle count. Changing viewport display quality does not lower the meshes used by this product renderer. Do not bypass the admission check or silently remove mechanical detail from the master. Use the whole native Design preview, isolate a component for current CPU PBR work, or author a separately documented reduced-visibility render scene. A future whole-rover render executor must admit the complete scene or explicitly support a documented mesh-detail policy.
CAD evaluation is additional to the render-job estimate. Opening the integrated stage-03 model during these checks took about 102 seconds and peaked near 3.76 GiB RSS. Later assemblies are larger. Run the native builder, validation and full-model image export **sequentially**. The provided tools constrain native worker threads and use an explicit allocator policy; detailed evidence records resource failures and recovered attempts rather than treating them as passes.
## Material and optical boundaries
- Opaque PBR values are authored visual parameters, not measured BRDF data. Painted orange and charcoal surfaces retain dielectric/coated appearances; exposed races, balls, contacts and shafts have separate metal responses.
- Engineering-material density is a nominal proxy. It does not establish the complete component mass, electrical specification, thermal performance or contact friction.
- Each glass vial is one hollow native solid with an integral bottom. This supplies coherent geometry for later closed-medium admission.
- Viewport opacity is not physical refraction. Camera lenses, diffusers and vials require explicit optical descriptors, lighting and medium validation; the current studio recipe above supplies and validates them without rewriting the native master.
- No lunar terrain, illumination ephemeris, dust simulation, HDRI or manufacturer PCB artwork is bundled in the native derivative.
## Preparing animation shots
Use the seven focused studies to inspect the actual joint chains. Hide the six rigid suspension coils and twelve motor leads while the suspension moves; these neutral render details do not deform. The `motion` profile in `presentation.py` selects that view. Removed arm hydraulic display cylinders do not reappear during motion.
Keep the final master neutral and export motion/render copies separately. The adapted author tour and focused studies are kinematic authoring resources. A complete animated shot still needs its selected combined path checked for collisions, a timing choice, flexible-detail treatment and any physical contact model it intends to claim. Throwing and catching a free vial remains future dynamics work.
+70
View File
@@ -0,0 +1,70 @@
# SELENE native source and lineage
The owner supplied **`SELENE-showcase.solidcraft`**, the improved mechanism demonstration published by **SychPL (Mateusz Sych)**. Its bytes match the file added to the author's `media/selene-showcase` branch on 2026-10-21. The unchanged source is retained at [source/SELENE-showcase.solidcraft](source/SELENE-showcase.solidcraft); [source-lock.json](source-lock.json) pins its identity and records the review.
The functional showcase is a new derivative of this file. The older `Rover-v7.solidcraft` remains a historical reference, not the modeling input.
## Exact identity
| Field | Verified value |
|---|---|
| Author repository | `SychPL/solidcraft` |
| Source branch | `media/selene-showcase` |
| Commit | `32ec1720447e3fcc6bb6cc53a63fb5b39cfa21b9` |
| Published timestamp | `2026-10-21T20:00:01Z` |
| Repository path | `SELENE-showcase.solidcraft` |
| Git blob SHA-1 | `a06fee2c8a839f5efccc75ae6de4e68f951cb266` |
| SHA-256 | `1b0df486f30da157db27a33e0b5ed67f13d527d4dfdc6c334717a1df4e5b8677` |
| Length | 314,452 bytes |
| Native container | ZIP with `document.json` and `container.json` |
| Native source format | `solidcraft/23` |
| Units | Millimeters; Z up. Stored angles are radians; joint commands accept degrees. |
The [source commit](https://github.com/SychPL/solidcraft/commit/32ec1720447e3fcc6bb6cc53a63fb5b39cfa21b9) identifies the design as Rover-v7 with closed-loop actuators, links, gears and the SELENE tour study. Both the Git blob hash and SHA-256 were computed independently from the supplied attachment on 2026-10-22.
Read-only container inspection found **3,378 features, 47 component definitions, 47 occurrences, 49 joints, nine motion links and one motion study**. These are source-document counts. They do not establish that every feature evaluates, that every body survives the timeline, or that the assembly is collision-free. Native evaluation and derivative checks are separate evidence.
## Following the original clue
The owner's [exact pinned mechanism reference](https://github.com/tangency-labs/solidcraft/blob/ac1768e08b83ed33212459951f5b50aa7a57cddc/skills/solidcraft-mechanisms/reference.md) describes the SELENE solar-actuator loops, wheel and solar motion links, grounded modules and visibility treatment. It also specifies command units, joint-origin conventions and validation methods. Those descriptions led to the author's showcase history.
| Evidence | What it establishes |
|---|---|
| [Native source at `32ec1720`](https://github.com/SychPL/solidcraft/blob/32ec1720447e3fcc6bb6cc53a63fb5b39cfa21b9/SELENE-showcase.solidcraft) | The exact improved design supplied by the owner. |
| [Parent media commit `df2e7423`](https://github.com/SychPL/solidcraft/commit/df2e74230e6659409d65501b5d9396b55c6503d3) | Three earlier GIF demonstrations, with credit to 0xlillith and Apache-2.0/MIT for the original model. |
| [Author's showcase comment on PR 1540](https://github.com/tangency-labs/solidcraft/pull/1540#issuecomment-6205857321) | The combined demonstration and the motivation for its added mechanisms. |
| [Motion export PR 1680](https://github.com/tangency-labs/solidcraft/pull/1680) | The exact showcase filename and tour/export examples. |
| [Original rover repository](https://github.com/0xlillith/solidcraft-rover/tree/b32437915372d32b71419dc8f5143fe22ff1e869) | The owner's original Rover-v7 model and its attribution. |
The source lock preserves the earlier bounded public search as historical evidence. That search had inspected the media branch before the native file was added. Its former `awaiting_native_source` outcome is superseded by the supplied file and the new author commit; it is not a current blocker.
## Authorship and notices
The [original rover attribution](https://github.com/0xlillith/solidcraft-rover/blob/b32437915372d32b71419dc8f5143fe22ff1e869/ATTRIBUTION.md) offers the original model under **MIT OR Apache-2.0** and credits the SolidCraft emblem to SolidCraft contributors. The original modeled lettering follows **Ubuntu Light** outlines by Canonical Ltd., under the **Ubuntu Font Licence 1.0**. Its exact notice is retained in [licenses/UFL.txt](licenses/UFL.txt). No font binary is added.
SychPL is credited for the improved assembly, helper geometry and showcase study.
On 2026-10-23T22:13:37Z, [SychPL explicitly licensed their additions](https://github.com/tangency-labs/solidcraft/pull/5494#issuecomment-6375788360)
under **MIT OR Apache-2.0**, at the recipient's option. The grant expressly covers
redistribution and derived files, including `SELENE-functional.solidcraft`.
The author media branch itself has no separate licence file; the linked public
author declaration supplies the previously missing permission. The original
rover and Ubuntu Font Licence notices above remain applicable and unchanged.
New procedural geometry, English documentation and command generators in this derivative are original contributions. Raspberry Pi references inform dimensions and component identity; manufacturer CAD, logos, PCB artwork, electrical netlists and photographs are not embedded. No commercial CAD assets or GPL geometry implementations are imported.
## Authoring runtime and format compatibility
The reconstruction workflow selects the official **SolidCraft v0.18.2 Linux x86_64 CLI**. Its [published release](https://github.com/tangency-labs/solidcraft/releases/tag/v0.18.2) identifies source commit `4927214976ff20758fde35af03bd6b2d6cf73e18`. The release archive SHA-256 is `9d0c922ac72204f34ad22ca7a171be78016ab002e304ace3c07d34d187d830ff`; the extracted CLI SHA-256 is `e50fe78292d569c36052b24f46f1732bc4e43d1ecdac6a64092aa1cee3b97086`. The executor is an external development dependency and is not bundled as a model asset.
This runtime writes **`solidcraft/28`**, which the personal render branch's format-29 reader can migrate. The unchanged source remains in format 23. The newer official v0.19.0 writes format 30, beyond that render branch's reader, so its isolated component fixtures remain development evidence rather than the authoritative final build. Final acceptance must name the actual executor and final model hash; success in a different runtime does not replace that run. No native format marker is rewritten by these tools.
## Reproducibility boundaries
- Keep the pinned source unchanged. Author geometry, appearances, components, joints and studies through SolidCraft commands into a separately named derivative.
- Record the executor version and binary hash with each native build. A newer native format is not backwards compatible merely because geometry looks similar. Do not edit a file's format marker to bypass this restriction.
- Bind exact evaluated body names and component identities. Feature declarations alone are not a body inventory.
- Verify joint movement and followers against independent calculations. A successful command without actual movement is not a successful mechanism test.
- Preserve execution failures and incomplete checks explicitly. Do not carry validation claims from a lost intermediate checkpoint into a newly rebuilt model.
- A native model suitable for inspection and render preparation is not a certified manufacturing design, an electrical design, or a physically simulated lunar rover.
The prepared appearance catalog, scene specifications and future dynamics sequence have their own evidence status. Admitting this source does not automatically bind or validate those resources.
+147
View File
@@ -0,0 +1,147 @@
# SELENE native validation
This report distinguishes evaluation of the complete saved rover, independent checks of its motion, reduced geometry fixtures and actual image generation. A clean feature timeline alone does not establish mechanical clearance, and a reduced fixture does not establish a collision-free whole rover.
## Native artifact and executors
The saved derivative is [models/SELENE-functional.solidcraft](models/SELENE-functional.solidcraft), **439,883 bytes**, native format **`solidcraft/28`**. Its SHA-256 is:
```text
3bd2bfa1c6336394c4a4208d30b52c80d06658a3bd7ea57cc93b774411d1bd68
```
The unchanged source is `source/SELENE-showcase.solidcraft`, 314,452 bytes, SHA-256 `1b0df486f30da157db27a33e0b5ed67f13d527d4dfdc6c334717a1df4e5b8677`. It is byte-identical to SychPL's published improved showcase file; [SOURCE.md](SOURCE.md) pins its author commit and license evidence. Native evaluation of that source found 1,418 bodies and no feature errors or warnings. Its container has 3,378 history features, 47 components excluding root, 47 occurrences, 49 joints, nine motion links and one motion study.
The authoring and final acceptance executor is the official SolidCraft v0.18.2 Linux CLI, SHA-256 `e50fe78292d569c36052b24f46f1732bc4e43d1ecdac6a64092aa1cee3b97086`. It writes `solidcraft/28`. The personal render branch's format-29 reader accepts that format. The native container is only read for inspection and selector construction; all CAD changes are made through SolidCraft commands.
The personal fork at `10ada1b45a4eb285ce752ac72f9aaa968ff8b5c4` was also compiled successfully and used for bounded product-render checks. Its CLI SHA-256 is `2a5485f7c7832d29b6340988837316e247b94eeeb9d4f7333ee616cea38c6826`. This separately built executable is not substituted into the authoring checkpoint chain.
## Complete-model build and motion acceptance
The full native build passed **17 saved stages and 2,318 native commands**, including the explicit inventory and graph inspections. Its final timeline has **zero errors and zero warnings**; all live body names are unique. The final native joint graph reports no errors, conflicts or floating occurrences.
| Native inventory | Supplied source | Functional derivative |
|---|---:|---:|
| Evaluated bodies | 1,418 | 2,116 |
| Components excluding root | 47 | 172 |
| Occurrences | 47 | 172 |
| History features | 3,378 | 5,484 |
| Joints | 49 | 180 |
| Motion links | 9 | 99 |
| Motion studies | 1 | 8 |
The final joint inventory contains 135 revolutes, 11 sliders, 33 rigid joints and one cylindrical joint. The saved document also contains **121 browser groups, ten named views and ten render setup records**. The build history and final native inventory are in `target/selene/native-build/`.
Independent final-master acceptance **passed all 33 cases and 880 numerical comparisons** in a fresh official v0.18.2 process. The run completed on 2026-10-22, from 08:40:24 to 08:53:15 UTC, with exit code 0. It exercised 26 kinematic/neutral cases and seven selected neutral-pose interference pairs. Both the master and original source remained byte-for-byte unchanged. All 19 independent drivers were neutral on open and after the final reset; the tested graph inspections reported no errors, conflicts or floating occurrences.
| Kinematic case family | Cases | Accepted exercise |
|---|---:|---|
| Drivetrain | 1 | Six wheels follow the driver; a 90° turn gives −62.831853 mm demonstration-track travel at the 40 mm nominal radius. Independent wheel and track world-position checks pass. |
| Solar deployment | 1 | The selected 20° deployment closes all four actuator loops and agrees with the independently calculated strokes. |
| Independent pointing | 2 | Sensor and dish pointing are driven independently, with native follower and world-position checks. |
| Gripper | 2 | Driver endpoints −15° and +18°, opposite follower values and jaw world positions. |
| Sample cap | 2 | 360° and 1440° rotation produce 1.5 mm and 6 mm axial travel respectively. |
| Suspension | 12 | Each of six carriers is driven to both −4° and +1.5°. Closed-loop strokes and carrier world centers of mass match independent calculations. |
| Rolling-element bearings | 5 | Shoulder, elbow, wrist, tool tilt and sensor elevation drivers exercise all ten bearing cages and 80 ball-spin relations, with one orbital witness per bearing. |
| Neutral reset | 1 | All 19 independent driver coordinates return to zero. |
| **Kinematic total** | **26** | **All passed.** |
The 880 numerical comparisons include repeated neutral-coordinate readbacks between cases; they are not 880 distinct poses. Numerical agreement is a solver check, not a manufacturing tolerance claim.
| Numerical quantity | Comparisons | Maximum absolute error | Acceptance tolerance |
|---|---:|---:|---:|
| Joint angles | 756 | 7.106 × 10⁻¹⁵° | 10⁻⁶° |
| Linear joint coordinates | 19 | 3.777 × 10⁻¹¹ mm | 10⁻⁵ mm |
| Suspension strokes, included above | 12 | 3.927 × 10⁻¹² mm | 10⁻⁵ mm |
| World center-of-mass coordinates | 105 | 4.443 × 10⁻⁵ mm | 0.002 mm |
| Bearing orbit coordinates, included above | 30 | 1.318 × 10⁻⁵ mm | 0.002 mm |
The largest linear error was a solar actuator stroke; the largest world-coordinate error was the dish yaw mount's center-of-mass height.
| Selected neutral-pose pair | Completed method | Result |
|---|---|---|
| `Shoulder link -9` / `Elbow link -9` | Exact solid intersection | Clear |
| `Elbow link -9` / `Distal wrist link -9` | Exact solid intersection | Clear |
| `Tool wrist machined tongue` / `Tool tilt joint shaft` | Exact solid intersection | Clear |
| `Gripper jaw -1` / `Gripper jaw 1` | Conservative broad-phase separation | Clear |
| `Raspberry Pi 5 PCB` / `Pi5 USB2 mounting tab 1` | Exact solid intersection | Clear |
| `Raspberry Pi 5 PCB` / `Pi5 USB3 mounting tab 2` | Exact solid intersection | Clear |
| `Raspberry Pi 5 PCB` / `Pi5 GPIO pin 40` | Exact solid intersection | Clear |
These are **six exact intersection checks and one broad-phase clearance**, all with zero reported overlaps, unchecked or skipped pairs, no truncation and no exhausted budget. They are selected checks at neutral, not an exhaustive swept collision analysis.
The complete local machine-readable report is `target/selene/validation-final/validation.json`, schema `1.0.0`, 228,492 bytes, SHA-256 `0ccbf279f27885f6a4646a691a03915ca9ae22ae2f6bfef9574b856392474130`. It records every case, comparison, drive and interference result. The validator's SHA-256 is `b032962210e86a516193bd549d9137dcff93dbff4a3e038ca9a08de3493e1aac`. The repository's resource rules keep raw execution reports in ignored `target/`; the shipped validator and committed case selection reproduce them.
The reproducible builder saves every completed stage, validates complete native command and timing traces, rejects timeline errors or warnings, requires unique live body names and checks the native joint graph. Resume verifies source, executable, recipe, result and checkpoint hashes. The independent validator opens the final saved file in a new process and checks requested values, follower values and world-coordinate measurements against separate calculations. It does not save its test poses over the master.
The selected interference pairs in [motion/interference-pairs.json](motion/interference-pairs.json) are deliberately bounded. Any timeout, unchecked pair or failed command is incomplete evidence, not a clearance pass. They do not exhaust all body pairs or all simultaneous configurations.
## Reduced-fixture evidence
These checks were completed before integrating the corresponding production commands. Their scope remains local even when the same geometry is present in the full rover.
| Subject | Actual evidence | Result and scope |
|---|---|---|
| Rolling-element kinematics | An arm bearing and a sensor-head bearing each driven by 30°; all eight relative ball spins and one orbital position checked in each case. | Arm cage 12.679558011° and relative ball spin −94.643843725°; head cage 13.360248447° and relative ball spin −135.576157538°. Maximum world orbital error 1.474 × 10⁻¹⁰ mm. Ideal no-slip relations, without contact-force simulation. |
| Bearing clearances | Eleven core arm/bearing pairs and four head-retention pairs: 14 completed exact Boolean checks and one bounding-box exclusion. Core fixture: 288 bodies, 736 features, 117 components, 115 joints and 90 links. | No overlap reported and no pair unresolved after retries. Both fixture inventories have zero feature errors/warnings. These official v0.19.0 fixtures precede the production arm set-screw tips' final 0.05 mm extension; the named local pairs do not establish all-body or all-pose clearance. |
| Raspberry Pi 5 | Official v0.19.0 fixture: 748 commands, 492 bodies and 1,202 features. Two analytic volumes, all 40 GPIO centers, six exact Boolean pairs and a separate broad-phase check of 26 major package envelopes. | PCB volume error 0.000514094% against a 0.01% tolerance; maximum GPIO center error 1.422 × 10⁻¹⁴ mm. No reported overlaps, unchecked pairs or timeline errors/warnings. Component layout remains a visual reconstruction. |
| Gripper | Native checks at −15°, 0° and +18°, plus a 661-point analytic sweep at 0.05° and a continuous vertex/stationary-point separation bound. | Minimum jaw-to-jaw gap 5.735342 mm; minimum pad-to-pad gap 6.360460 mm. The local jaw envelopes remain in opposite half-planes over the continuous driver interval. Pin/sleeve radial clearance is 0.05 mm. This is a local gripper proof. |
| Suspension | Retained-source-label fixture: 71 commands, 15 bodies, 43 features; both −4° and +1.5° endpoints; 12 pair-pose checks over eight unique pairs, including six exact Boolean intersections. | No timeline errors/warnings or reported overlaps; no unchecked pairs. Maximum slider-equation error 3.927 × 10⁻¹² mm and world center-of-mass error 1.973 × 10⁻⁹ mm. |
| Yaw journals | Official v0.18.2 fixture: 101 commands, 20 bodies, 67 features; six support-only rotation cases, 44 pair-pose checks over 26 unique pairs, including 29 exact Boolean intersections; nine volume checks and 12 seating/distance measurements. | All cases passed; no timeline errors/warnings, unchecked pairs or reported overlaps. Maximum analytic volume error 0.0155823%, within the 0.02% tolerance. |
| Integral glass vials | Official v0.18.2 fixture: 55 commands, six cups, one component and 49 features. The 22-command production subrecipe matches the validated recipe exactly. | Maximum analytic volume error 0.0080213%, COM error 0.000108088 mm and bounding-box error 0.000155825 mm. Each cup has one kernel shell; names, ownership, Glass material and opacity 0.30 are preserved. |
The yaw fixture exposed an existing overlap of approximately 21.33 mm³ between each of four dish mounting screw heads and their stationary support. Four local reliefs remove those overlaps. The support-only fixture exercises arm/turret ±160° and dish ±150° to inspect the axisymmetric interfaces; the complete rover retains its narrower documented joint limits. Those local rotations do not expand the rover's accepted motion envelope.
The journal supports have nominal 0.1 mm radial journal clearance, 0.15 mm radial pin clearance and 0.1 mm axial retention play. Display-mesh distance measurements of the tiny retainer gaps were 0.0644–0.0826 mm. These are mesh approximations; the nominal analytic dimensions and completed solid-interference results are recorded separately.
The vial fixture also exported 57,264 native STL triangles. After coordinate welding at 10⁻⁶ mm, the mesh has six connected components and zero boundary, nonmanifold or inconsistent-winding edges, and zero degenerate triangles. Each vessel retains its 11.6 mm outside diameter and 27 mm height, with a 0.8 mm wall and 1 mm integral floor. Its fixture SHA-256 is `76f0cb4e25e7beb2f04a1ccd84619144441f9d580129f0325c29ef8d774ef2a6`; detailed evidence is in `target/selene/vial-repair/validation-report.json`. Mesh closure does not establish physical dielectric renderer support.
Two initial bearing checks exhausted their eight-second budgets. With 30-second budgets, the shoulder-clevis/outer-race check completed in 12.122 seconds and the ball/inner-race check in 10.382 seconds, both clear. The original timeout reports remain alongside the successful retries. Their initial zero-overlap counts were not accepted as passes.
The electronics and core bearing fixtures use official v0.19.0, format 30; they are development fixtures, not the distributed master. Their production commands were subsequently replayed in the complete rover with v0.18.2. The retained-label suspension fixture's SHA-256 is `1da6c92ba9ff2c421dc5b475f9971b458d3d43bbb048147df0cdeb70fefb9a11`; the yaw fixture's is `65ba6b033e301bd6cc4c91caa31d2ea67c69064d970355ff0597609adc62efcd`. The unchanged electronics generator is `67a69a7e613ad59dcf474dc6ecd9d9fb8439aa193e2e2ee458c282b65186fd9d`; its fixture is `578d9ba7b00738552ef9201c57489520fd86275c7fcfa23818adcdaed07aae04`.
Local report locations in a development checkout are `target/selene/bearings/compact-phase-report.json`, `target/selene/bearings/review-gripper-presentation.json`, `target/selene/electronics/validation-report.json`, `target/selene/suspension/retained-labels/validation-report.json` and `target/selene/yaw-support-audit/validation-report.json`. Detailed generated logs, fixture files and diagnostic images stay outside the production asset package.
## Image evidence
The compiled personal fork reopened the complete final master and generated three actual **1400 × 1000 native Design PNGs**. All three were visually reviewed and are shipped under `exports/`. The overview shows the neutral rover, the electronics view exposes the connector mouths and populated board, and the bearing view isolates its races, balls and cage. The capture opened all 2,116 bodies with a clean timeline and left the master hash unchanged.
| Shipped preview | Bytes | SHA-256 |
|---|---:|---|
| [SELENE-overview.png](exports/SELENE-overview.png) | 423,686 | `44d317ca9b0f77c3ac5d43409f0b6b281b9d6724d58a3c8edd8ca2db19a0bc63` |
| [SELENE-electronics.png](exports/SELENE-electronics.png) | 225,933 | `cd8af0700ce6a4cc0aa00bdfa5d246e4b3ed491847795636c65d9a8ead45991e` |
| [SELENE-bearing.png](exports/SELENE-bearing.png) | 284,094 | `ed3a3da01946019c9c380bf8515013eca3a05c7bf5161ff4bb98ee36eb5ef92b` |
The native capture metadata identifies the CPU rasterizer and its expected warning: surface-analysis colors, highlights and legends are not drawn. These previews do not claim physical PBR or transmission. Cameras, executor hash and capture metadata are recorded in `target/selene/final-previews/export-report.json`; [tools/export_views.py](tools/export_views.py) reproduces the three selections without saving transient visibility or camera changes over the master.
Two actual `cpu-product` renders from the compiled personal fork passed preflight and image generation with no warnings: a single bearing fixture (20 visible bodies, 93,654 triangles) and the Pi isolated from the integrated rover checkpoint (504 visible bodies, 103,004 triangles). Both outputs were 768 × 576 PNGs and were visually inspected. The bearing is a reduced fixture; the electronics image uses actual integrated geometry. See [RENDER.md](RENDER.md) for exact resource estimates, rendering scope and limitations.
The complete rover exceeds this revision's 262,144-triangle CPU product admission limit. The 64 arm-bearing balls alone contain 654,336 reference-mesh triangles. That code path has not produced a whole-rover PBR image. Native Design previews and bounded product renders are different evidence.
## Repository checks
The catalog and prepared-resource validators passed with the source status `pinned_native`. The wider scene catalog intentionally remains `runtime_ready: false`: the native model's completion does not bind every proposed stage, material role or future dynamics event.
The required repository-wide `cargo xtask ci` command was attempted with Rust 1.95.0 and the required socket permissions. Formatting, asset attribution, icon checks (17 tests), license checks, generated lists and dependency-layer checks (17 crates) passed. CI then stopped at a pre-existing `clippy::nonminimal_bool` diagnostic in `crates/geom/src/fill_general.rs:166`, under `-D warnings`. That application source is unchanged by this work. Later workspace tests and WebAssembly checks were not reached. The whole repository gate is therefore **not green**; the successful native CLI build and asset checks do not replace it.
The final `cargo xtask ci` attempt, after adding the shipped master and all three previews, reproduced that same baseline Clippy failure. Its exact output is retained locally in `target/selene/ci-final.log`. Its asset and icon gates passed. The catalog and prepared-resource validators also passed with zero errors, and the final whitespace check was clean. No application Rust source is changed by this package.
## Resource failures and recovered attempts
The working environment has an 8 GiB memory limit. Three earlier native attempts were killed for exceeding it: one overlapped a second cold full-model open; two involved late renames of original suspension bodies. Late original-body renames forced a long history rebuild while prior evaluated state remained resident. The production recipe retains those original labels and splits suspension geometry into smaller saved stages. Geometry, ownership and joint intent remain explicit.
Full-model native processes now run sequentially. The provided tools set two native worker threads and an allocator policy of `MALLOC_ARENA_MAX=1`, `MALLOC_MMAP_THRESHOLD_=65536` and `MALLOC_TRIM_THRESHOLD_=65536`. These are process-memory settings, not geometry simplifications. Failed attempts and their logs are retained under `target/selene/failed-attempts/`; they are not counted as successful validation.
Strict container inspection also rejected one intermediate suspension checkpoint because the inherited tour still referenced the original wheel joint ID after wheel reparenting. The builder now refreshes that study by exact joint names before saving the suspension checkpoint. The corrected stage completed with 172 components and 180 joints, and the rejected file remains diagnostic evidence only.
The first presentation attempt failed at the original touching vial-wall/bottom union and saved no presentation checkpoint. The validated replacement removes each split pair and creates an envelope with a blind bore, retaining the original wall name, external dimensions, component and appearance. Its base coordinates account for the source's already baked `Move9` translation. The failed union log remains under `target/selene/failed-attempts/vial-touching-union/`.
## Remaining physical and animation scope
The rover is an editable geometric and kinematic showcase. The rolling bearings use ideal linked revolutes; the suspension uses rigid links and a closed pin–slider–pin loop. Neither provides contact forces, spring dynamics, damping, backlash, stress, fatigue or a rated load capacity. Fasteners have nominal seats rather than fully modeled thread engagement.
The six rigid spring solids and twelve motor leads remain neutral-pose render details and must be hidden for suspension movement. The motion visibility profile handles this. Flexible hose display geometry and rigid decorative arm hydraulic cylinders are excluded from the relevant moving presentation.
The Pi is an original visual reconstruction based on cited public mechanical dimensions. It is not manufacturer CAD, a PCB netlist, exact component artwork or an electrical design. Opaque PBR parameters are authored appearances; nominal material density does not establish measured mass, thermal behavior or friction. Glass viewport opacity is not a physical transmission model.
The adapted legacy tour and seven focused studies are animation authoring resources. Their presence does not certify every frame, every combined pose, obstacle clearance or a future throw-and-catch sequence. Those checks belong to the specific animation shot and any physical simulator chosen later.
+96
View File
@@ -0,0 +1,96 @@
-------------------------------
UBUNTU FONT LICENCE Version 1.0
-------------------------------
PREAMBLE
This licence allows the licensed fonts to be used, studied, modified and
redistributed freely. The fonts, including any derivative works, can be
bundled, embedded, and redistributed provided the terms of this licence
are met. The fonts and derivatives, however, cannot be released under
any other licence. The requirement for fonts to remain under this
licence does not require any document created using the fonts or their
derivatives to be published under this licence, as long as the primary
purpose of the document is not to be a vehicle for the distribution of
the fonts.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this licence and clearly marked as such. This may
include source files, build scripts and documentation.
"Original Version" refers to the collection of Font Software components
as received under this licence.
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to
a new environment.
"Copyright Holder(s)" refers to all individuals and companies who have a
copyright ownership of the Font Software.
"Substantially Changed" refers to Modified Versions which can be easily
identified as dissimilar to the Font Software by users of the Font
Software comparing the Original Version with the Modified Version.
To "Propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification and with or without charging
a redistribution fee), making available to the public, and in some
countries other activities as well.
PERMISSION & CONDITIONS
This licence does not grant any rights under trademark law and all such
rights are reserved.
Permission is hereby granted, free of charge, to any person obtaining a
copy of the Font Software, to propagate the Font Software, subject to
the below conditions:
1) Each copy of the Font Software must contain the above copyright
notice and this licence. These can be included either as stand-alone
text files, human-readable headers or in the appropriate machine-
readable metadata fields within text or binary files as long as those
fields can be easily viewed by the user.
2) The font name complies with the following:
(a) The Original Version must retain its name, unmodified.
(b) Modified Versions which are Substantially Changed must be renamed to
avoid use of the name of the Original Version or similar names entirely.
(c) Modified Versions which are not Substantially Changed must be
renamed to both (i) retain the name of the Original Version and (ii) add
additional naming elements to distinguish the Modified Version from the
Original Version. The name of such Modified Versions must be the name of
the Original Version, with "derivative X" where X represents the name of
the new work, appended to that name.
3) The name(s) of the Copyright Holder(s) and any contributor to the
Font Software shall not be used to promote, endorse or advertise any
Modified Version, except (i) as required by this licence, (ii) to
acknowledge the contribution(s) of the Copyright Holder(s) or (iii) with
their explicit written permission.
4) The Font Software, modified or unmodified, in part or in whole, must
be distributed entirely under this licence, and must not be distributed
under any other licence. The requirement for fonts to remain under this
licence does not affect any document created using the Font Software,
except any version of the Font Software extracted from a document
created using the Font Software may only be distributed under this
licence.
TERMINATION
This licence becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
DEALINGS IN THE FONT SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More