Compare commits

..
274 Commits
Author SHA1 Message Date
bherbruck 069ba1223f Release 0.10.1 2026-10-14 12:58:23 +00:00
bherbruckandClaude Opus 5.5 4c09fa5355 Hovered and selected faces light solidly again over GPU-drawn bodies (#1064)
Since the model's mesh went back to the reference mesher (#940), the GPU viewport drew
opaque bodies from their display plans while hover and selection lit faces with the
reference mesh's triangles. Two triangulations of the same face at equal depth fought, so
a hovered face shimmered in wedges instead of lighting whole.

Faces lit over a body drawn from its plan now use the plan's own triangles
(ModelBody::plan_mesh, evaluated once on the CPU), and the highlight pass gets a small
depth bias toward the eye so f32 rounding between the GPU and CPU evaluations of the same
triangles cannot speckle it either.

Test: every face of a box with a pocket and a round boss, hovered and rendered offscreen
through the viewport's real pipelines on the GPU path, lights the same pixels as on the
CPU path (skipped without a GPU adapter).

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:56:47 +00:00
bherbruck 626fe6fdee Release 0.10.0 (#1010) 2026-10-14 05:38:13 -04:00
selune 01f2c78cd1 Validate sourced physical material property cards and elastic stability (#934)
Adds the read-only material.properties.validate command: schema_version 1 engineering material cards in SI units are checked for numerical consistency and elastic stability, with derived isotropic moduli, missing conditions and sources needing review. Nothing is assigned or saved.

Thanks @0xlillith.
2026-10-14 05:28:18 -04:00
Karl Von Richter 9ca7d307a4 Offset of an irregular closed spline no longer fails its error certificate; offset fit nodes stay private (#1009)
Fixes #971. At the node limit the offset certificate splits the fitted curve where it meets the exact offset midpoint, so curves with different parameter speeds certify within the same 0.002 mm tolerance. Associated offset fit nodes are hidden from display, picking, box selection and snapping until Break Association.

Thanks @Karl-VonRichter.
2026-10-14 05:27:39 -04:00
bherbruck 2f91338d3d Large STEP imports: no more multi-minute hang, shared STEP text, cancel and progress (#925) 2026-10-14 05:01:12 -04:00
bherbruckandClaude Opus 5.5 dfbf7dd6a8 fix: a lenient parameter boundary found across a seam is not used; record seed 81 (#1003)
#970's lenient retry of a face's parameter boundary searches the whole surface as a last resort; on robustness seed 20 it found an edge's points across a cylinder's seam, half a turn apart, and the Cut failed. A lenient boundary whose points jump half the parameter range or more is refused, and the face is left undivided as before (#970's elbows step at most a fifth of the range). Seed 81's volume moved by 0.1 mm3 with #952's hole direction; recorded.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:56:39 -04:00
Mateusz SychandSychPL cf9d7772f1 To / From Object finds a target in another, moved component (#993)
Fixes #928. Extrude To / From Object and Revolve To Object now resolve their target in the feature's own component frame, so a face in another component whose occurrence is moved is found where it is shown instead of the extrude stopping at a flat plane through the picked point. The point is named after the nearest face, a named point keeps its named face when another component touches it there, and such features re-evaluate when a placement changes, so they follow the target when it moves later.

Co-authored-by: SychPL <SychPL@users.noreply.github.com>
2026-10-14 04:41:59 -04:00
midasdfandmidasdf 6e84c03946 Curvature-continuous (G2) fillets for straight planar edges (#862)
Fillet gets a Curvature (G2) radius type (type: "curvature", alias "g2"): a quintic blend whose size is the equivalent circular setback, for convex and concave straight edges between planar faces with a perpendicular planar end face at each end. Unsupported cases are refused rather than falling back to a circular fillet, and a blend that would cross a loop in its end face is refused. Persisted as a fillet style under format 19; labels in en/de/ja/ko/zh.

Co-authored-by: midasdf <midasdf@users.noreply.github.com>
2026-10-14 04:26:53 -04:00
froglemonade2andfroglemonade2 3669d4b3d0 Cut a hole tangent inside a stepped boss (zero-thickness curved wall) (#986)
Touching sews now split 4-use straight edges on extruded walls (along their rulings), not only on planes. Faces round such an edge are ordered by the way they leave it and, where they leave it tangent, by their curvature. Sheets bound the material wedges as before, except where that would make a face touch itself (a hole touching the outline from inside); edges ending there pair the empty wedges instead, so each loop gets its own vertex. Coincident faces are still refused, plain sew still refuses vertex contact, and shell_groups is unchanged.

A Ø10 hole tangent inside a stepped Ø40/Ø20 boss now cuts exactly instead of failing with "wire is not simple". Robustness seeds 42 and 51 are recorded: their touching hole patterns in stepped bodies now cut exactly instead of by a tool nudge.

Co-authored-by: froglemonade2 <froglemonade2@users.noreply.github.com>
2026-10-14 03:55:06 -04:00
NOWN-DEVandNOWN-DEV 345c0882cd Renders hide edges just behind a face; lines on steep faces draw whole (#983)
The CPU renderer's line depth test allowed a fixed share of the scene's depth range (2e-4), which on large designs or close views let edges a millimetre or more behind thin sheet show through it. Each pixel now keeps the screen depth slope of the surface it shows, and a line passes where it is no further behind than that slope spans across the line's width, plus f32 rounding. Section caps use their plane's slope.

Edges lying on steep faces now draw their full width; edges behind a face stay hidden. Only crates/render/src/raster.rs changes; the GPU viewport is unchanged.

Co-authored-by: NOWN-DEV <NOWN-DEV@users.noreply.github.com>
2026-10-14 03:28:31 -04:00
bherbruckandClaude Opus 5.5 18347b3d0d Boolean nudge: no sliver where a tool only touches a round side (#827, seen in #897) (#941)
* Boolean nudge tried both ways: no sliver where a cut's end touches a round side (#897, #827)

Robustness seed 14 cuts a cylinder whose end lies exactly on the apex of the
plate's arc side. Since the display tessellation plans (#805) the generator sees
the arc's true extent, so the cut's base lands on -10 instead of -10.000000000000002
and the end is tangent to the arc. The direct boolean fails there and the
last-resort tool nudge pushed the end into the arc, leaving a planar sliver a
tenth of a millimetre wide; shell then picked that sliver and refused it. The
13 October merges were not the cause (the bisect lands on #805).

The nudge is now tried both ways and the simpler result (fewer faces) is kept,
so the opposite nudge parts the touching faces instead. The same change joins
the hinge barrel in #827, whose top lies tangent to the lid's underside where
the mounting leaf ends. Each result still passes the same checks as before.

Goldens: seeds 14, 26 and 85 now build to the end, 31 and 78 come out with
fewer faces. Seeds 6, 43 and 73 still stop at shell: their joins now succeed and
shell reaches corners it can't offset (a cusp where a box face touches an arc,
coplanar faces facing opposite ways), which is unchanged.

Fixes #897
Fixes #827

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

* Nudge the other way only when the first nudge fails or leaves a sliver

Trying both nudges and keeping the one with fewer faces could prefer a result
that keeps two pieces joined by a hair over one that separates them (the
centred coplanar revolve cut). The opposite nudge now runs only when the first
one failed or left a face narrower than 1/200 of the part, and it is taken
when it works with fewer of those.

Goldens re-recorded on current main: seeds 14, 26 and 85 build to the end,
31 and 78 with fewer faces. The other changed seeds are main's own since its
last full record, identical with and without this change.

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

* Nudge the other way only for slivers the nudge made

On the sensor enclosure's lid the first nudge left thin faces the operands
already had (thin walls, small rounds), so the opposite nudge ran a second
boolean for nothing and the press/pull took 79 s against main's 55 s. The
opposite nudge now runs only when the first one failed or left more slivers
than the operands brought; the press/pull test takes 36 s against 42 s on main
(same machine, one after the other).

Goldens re-recorded on top of #940.

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

* Goldens on current main: seeds 31, 43 and 78 with the two-way nudge

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

* Keep the hinge and tangent-cut tests beside the #871 fillet test

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

* Goldens: seeds 33 and 91 as main's fillet changes build them

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

* Goldens: keep main's #950 record

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:55:37 -04:00
bherbruckandClaude Opus 5.5 ac84491638 test: web-offline-test reads the worker's caches without opening them (#982)
The state probe opened every cache it listed; when a worker deleted one in between, the open made it again, empty, and the next wait timed out. The probe now reads each cache with caches.match over the builds' files and marks, which never makes a cache (3 failures in 20 runs before, 0 in 30 after). The worker only ever starts a complete build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:54:45 -04:00
NOWN-DEV 6a67cc8f1e An edge flange folds beside a ledge as tall as its setback (#978)
Trimming the base at an edge flange's setback snaps corners within tolerance onto the trim line and merges away the zero-width run a ledge as tall as the setback left along it, so the ledge goes with the strip as a lower one does. Splitting outline edges at other cells' corners now skips points within the model's tolerance of an end, so a ledge a little taller keeps its sliver and the bend runs under it, also off the axes. Both used to fail with "sew: a degenerate edge". Nothing new is stored.
2026-10-14 02:51:29 -04:00
bherbruckandClaude Opus 5.5 6cb3025b72 Sheet metal: a hole drilled into a rim is a bore its panel keeps (#560) (#955)
A hole drilled along a panel, into its rim, has no place in the flat pattern
as a cut-out, so every such hole was refused. It is now kept as a bore of that
panel, in flat coordinates, and cut only from that panel's material when the
folded part and its flat pattern are built, so the sheet still unfolds and
folds back with it. A hole that would run into a bend, or cross a panel at an
angle, is still refused. The flat pattern lists the bores.

The action matrix worker takes ACTION_MATRIX_ONLY (just these actions) and
ACTION_MATRIX_UNFOLD (unfold and refold every sheet after each action) for
rechecking one group by hand.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:31:19 -04:00
bherbruckandClaude Opus 5.5 b34d45233d Holes on round faces from the app, and Flip for holes at sketch points (#980)
* Holes on round faces from the app, one hole per pick, and Flip for holes at sketch points (#967)

The Hole dialog takes any face: a hole on a round face drills against the face's normal where it
was picked, and its arrow shows that. A round face split at its seam is picked whole, but makes
one hole where it was clicked. Holes at sketch points get Flip (saved as flip in the hole's points,
under format 18), in the command and in the dialog.

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

* Holes at sketch points show their arrow: into the part, or away with Flip (#967)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 02:10:09 -04:00
Karl Von Richter 47a7f29485 Guide sketch patterns with live previews and editable offsets (#966)
Sketch Mirror, rectangular and circular Pattern, and Offset now show a green live preview that follows the selection and settings; OK commits, Cancel leaves the sketch unchanged. The shared dialogs show the current selection step and source counts, sources can be toggled with Ctrl/Cmd-click, cleared and reselected.

Offsets keep editable driving distance dimensions (One Side, Two Sides, Symmetric) that update the derived curves without moving the source, with undo/redo and save/reopen. Format 19: older files keep their fixed offsets.

New dialog labels in en, zh, ja, de and ko.

Fixes #696. Fixes #517.
2026-10-14 01:58:01 -04:00
Giovanni 9c51f9d433 Retry a face's parameter boundary leniently (#970)
Cutting one lofted round tube from another failed in truck's divide_face ("parameter boundary") for some section counts: the intersection points lie on the loft's seam, the previous point's parameter can sit a hair outside the range, and the Newton search from there fails.

The boundary is built as before; only when that returns None is it built again leniently, starting each search from the hint clamped into the parameter range and, as a last resort, searching the whole surface. Nothing that succeeds today changes. The boolean-retry guards are untouched.

Fixes 5 of the 6 failing hollow-elbow cases in #968 (new test crates/engine/src/tests/loft_cut.rs, volumes within 0.3% of exact). 90 degrees / 14 sections still fails in a later step and stays open in #968.
2026-10-14 01:50:00 -04:00
NOWN-DEV 4687e3b073 Convert to Sheet Metal keeps a plate's round holes round (#977)
The plate check compared the face outline's area (from the tessellation, so each hole was a polygon of chords) with the exact volume, and refused plates with a few small holes. Where the outline runs along a cylinder about the plate's normal it is now an arc of that circle; coaxial pieces of one radius are one wall, so a hole split into two, three or four faces is one circle. The check passes with its tolerance unchanged, and the flat pattern and its DXF carry one CIRCLE per hole (ARC for rounded corners and slot ends).

A round blank now converts too, so unfold_cut_refold_and_convert checks the refusal with a block thicker at one end instead of a rod.
2026-10-14 01:47:40 -04:00
bherbruckandClaude Opus 5.5 1ff16cc72b A hole on a face drills into the material, also in older designs (#950) (#952)
* Hole matrix: placement, types, extents, edits and 0.9.0 designs, with today's failures listed

Each case checks the drill goes into the material, the bore sits on its axis, a blind hole stops
where it should, the removed volume against its analytic value, the face count and that every
body is still one closed valid solid. Failing cases are listed in tests/hole_matrix/known_failures.txt
as a ratchet.

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

* A hole on a face drills into the material, also in older designs (#950)

Without a direction, solid.hole took the direction from any face whose infinite plane passed
within ten tolerances of the point, the last one found winning: the far side of a thin plate or a
face of another body at the same height could set it, and the wrong direction was saved. It now
uses the face nearest the point, against its outward normal (exact for planes, cylinders and
spheres, so holes on curved faces work without a direction). When a design is evaluated, a face
hole whose saved direction leaves the face it starts on is mirrored into it, so designs saved
with the wrong direction drill into the part.

Fixes #950.

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

* Hole at a point on no face (a planar face's centroid outside it) keeps the plane it lies in

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

* Record the hole direction fix in the goldens and the action matrix baseline (#950)

Seeds 7, 21, 81 and 85 drill their holes straight into their faces now. In the action matrix,
holes on four faces that used to go the wrong way now build, and two whose correct direction
meets a cut the kernel refuses are marked failing.

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

* Goldens: seeds 7, 21 and 85 drill their holes straight in (#950)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:45:11 -04:00
bherbruckandClaude Opus 5.5 813bee8914 Fillet: refuse a result with openings in its surface (#871) (#949)
* Fillet: refuse a result with openings in its surface (#871)

A fillet on #871's part (radius 1 on a window's edges and on the sides of a
block 1.26 mm away) came back closed as topology with every face meshing, but
the rounds overlapped and the end faces' boundary loops crossed, so the model
showed holes. The constant-radius fillet now checks its result before taking it:
the body's mesh, welded within a millionth of its size, must have no edge
without as many triangles running the other way along it, except for cracks
within the mesh tolerance where fitted curves meet. A result that fails is
refused, so the corner build gets its try and the radius search treats it as
too large.

Body::open_mesh_edges is the check. Kernel tests cover the #871 part (refused
at radius 1, closed with a round on every edge at 0.5), and an engine test runs
the fillet from the report and leaves the part as it was. All 96 robustness
seeds build the same with and without it.

Fixes #871

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

* Check fillets for crossed face boundaries, not free mesh edges

Free edges in the mesh also come from cracks where fitted curves meet (corner
patches, rounds ending on curved edges), so the mesh test refused good fillets.
The check is now on the cause of #871: a planar face whose boundary loops cross
each other or themselves has no inside. Body::crossed_face_loops samples each
planar face's loops within 1/1000 of the size and counts proper crossings
(touching within a millionth doesn't count).

The pyramid apex blend and the loft fillets build again. Ten action-matrix fillets
stay refused, recorded in the baseline: r = 3 on the 2 mm rim of oracle 09's
shelled box (the round runs past the inner wall; the result was 240 mm3 lighter
than any round can make it), and the six seam-edge fillets on oracle 60's bolt
holes, whose boundary jumps across the hole.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:36:30 -04:00
Mateusz SychandKarl-VonRichter 482abb4562 test(mcp): compare the scratch folder by its canonical path (#965)
PathScope stores its roots canonicalised (\\?\ on Windows, /private/var on macOS), so the default-folders test now canonicalises scratch_dir() before looking it up, like the recovery folder on the line above.

The same fix came in from @Karl-VonRichter in #969, found on macOS.

Co-authored-by: Karl-VonRichter <20079846+Karl-VonRichter@users.noreply.github.com>
2026-10-14 01:26:51 -04:00
7bed28752e Sheet metal: fold a second tab on a line, and a tab on an L's inside edge (#638, #639) (#963)
- A fold's zone clipped across the root of a tab folded before on the same
  line kept a sliver of no width there, which made the zone look like it
  crossed a slanted edge. The one real piece is the zone now (#638).
- When the tabs a fold line spans can't take the side beyond them along,
  because it joins the side that stays through other material (a tab on the
  inside edge of an L, the fixed point on the far leg), the tabs' own ends fold
  instead, the same part as with the fixed point on the near leg (#639).

Co-authored-by: jonaswre <3731681+jonaswre@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:16:04 -04:00
bherbruckandClaude Opus 5.5 ea23401710 Sketch on every side wall of one sketch extruded up and down (#957)
* Name a side drawn on several sketch curves by all of them

An extrude named each side wall after the one sketch curve its profile
segment lies on. A wall along two coincident lines (squares drawn edge to
edge) or along collinear lines merged into one profile segment had no
single curve, so it was named from geometry and could not carry a sketch
attachment. One sketch extruded up and down and joined (the logo) refused
a sketch on every one of its side walls.

Such a side is now named by every curve it lies on, in sketch order: the
first is its name, the others aliases. The set is fixed by the sketch, so
the name is exact. The refusal message no longer blames merged faces.

Adds the reported logo design and a synthetic up-and-down join to the
action-matrix designs, and a regression test that sketches and extrudes on
every flat face, fresh and after save and reopen.

Fixes #954

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

* Action matrix: record the up-and-down join designs

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 01:00:04 -04:00
Nathan Dane 9f76cca8b4 Collect application resources for #433 (#833) 2026-10-14 00:49:32 -04:00
d5ff5ea1a5 Sheet metal: a thousand cut-outs fit the flat budget, ten times faster (#732) (#964)
- A new cut was clipped against the panel with every one of its cut-outs, so
  each hole of a perforation cost the square of all the holes before it and the
  second cut of 500 ran out of the flat budget. A tool is now clipped against
  only the cut-outs whose boxes meet its own (the same result).
- The planar booleans' point-in-loop test now skips loops whose box is clear of
  the point, before the costly on-the-loop test; with a thousand holes that test
  was most of the time.

The 1000-hole panel now takes about 15 s instead of failing.

Co-authored-by: jonaswre <3731681+jonaswre@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:40:08 -04:00
bherbruck ce902be66c Fillets and chamfers ending on a boss's pieced wall or beside it, on a face that runs along its seam twice, and loops the loop blend refuses (#926)
Fillets and chamfers ending on a boss's pieced wall or beside it, on a face that runs along its seam twice, and loops the loop blend refuses

- A straight edge whose end meets one curved surface in several faces (a cylinder a join left
  in pieces) ends on that surface: the section is cut where it crosses each seam, which is
  shortened there; where no seam is crossed (the wall missing inside the other body) the
  section lies on the bared surface and the rest of the side edge becomes a seam.
- Two end faces that aren't both planes meeting in lines: each face takes the section's points
  on it, cut where it crosses the edge between them. Not where an end face runs on smoothly
  from a side (an earlier round): the corner build takes those.
- Face boundaries close over several connector edges.
- A face on a periodic surface running along its seam twice (as a STEP file gives it) is
  rebuilt the same way (#691).
- A curved chain ending square on a face that runs on past its side extends that face's seam.
- A smooth chain stops before an edge between faces that meet smoothly.
- A loop the loop blend refuses for its shape is blended edge by edge; refusals about its size
  stand.
- Action matrix: ACTION_MATRIX_ACTIONS and ACTION_MATRIX_VALIDITY for quick local runs; the
  fillets and chamfers that pass now recorded.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:28:09 -04:00
63095fedb3 Sheet metal: a fold's fixed point on the sheet's face counts as on it (#685) (#958)
On a large thin sheet the probes around the fixed point stepped clean through
the sheet (twice the mesh tolerance of a 465 mm part is more than its 0.8 mm
thickness), so a point exactly on the face touched no material and the fold
was refused. The probes now also step a quarter of the thickness.

Co-authored-by: jonaswre <3731681+jonaswre@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:14:49 -04:00
bherbruckandClaude Opus 5.5 00fdcf3e72 fix: grid lines for split ruling edges only in display and export meshes; record the moved seeds (#961)
#921's grid lines through the vertices that split a ruling edge also went into the meshes Boolean operations intersect, which changed seven robustness seeds (seed 2's sphere cut failed). Deflection::rulings now asks for them only in the kernel's own tessellation (display, export, measurement). The seeds #936 (rejected instead of unsupported), #946 (26 and 85 build, others fail differently) and #921's display side (about 1e-6) moved are recorded with their reasons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 00:14:18 -04:00
bherbruckandClaude Opus 5.5 774a8b1d9a test: record the bracket example's action matrix after its fillet moved (#962)
#820 fixed examples/bracket.json's fillet pick, which lay far from every edge and rounded the outer corner instead of the inner one. This re-records examples/bracket only: its edges carry the fillet's new names, and three chamfers beside and across the inner fillet are refused (the blend is larger than the neighbouring faces).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:57:59 -04:00
bherbruckandClaude Opus 5.5 97ff7578c2 Rounded Rectangle sketch tool (#891) (#948)
* Rounded Rectangle sketch tool (#891)

A new SKETCH > CREATE command, sketch.rectangle.rounded, in the Rectangle
family: two opposite corners and a corner radius make a 2-point rectangle
whose four corners are filleted alike, as one undo step. The radius is one
parameter (one radius dimension, the other arcs equal to it), so changing it
changes all four corners. While drawing, Width, Height and Radius boxes take
typed values; typed width and height dimension the overall size between
opposite sides. Without a typed radius the corners get a fifth of the shorter
side.

Fixes #891

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

* Rounded Rectangle in the Simplified Chinese catalog

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:52:10 -04:00
HEOJUNFO 19bf854dc9 Loft: pair section vertices by best fit (#881) (#956)
Each section now starts at the rotation whose vertices line up best with the lead section's, vertex for vertex, instead of at the one vertex closest in direction to the lead's first. Fixes the twisted loft of matching non-convex sections; regression test loft_pairs_the_vertices_of_matching_non_convex_sections.
2026-10-13 23:50:45 -04:00
HEOJUNFO f204042bf2 Preferences › Sketch: auto-projection holds across designs (#680) (#959)
Session::replace_design keeps auto_project when file.new, doc.open and doc.recover swap in another design, and the UI keeps it across tabs, adopt and the last-tab close. Regression tests in engine (sketch_project) and ui-egui (documents).
2026-10-13 23:49:51 -04:00
Jan Szumiec 4bea4c7785 Make the web app work offline and update itself (#239) 2026-10-13 23:40:19 -04:00
selune 09b5da3e9f docs: propose extensible material, appearance and component libraries (#792) 2026-10-13 23:22:58 -04:00
Lasse Lianandlasselian a2c7396160 Fillets and chamfers where edges meet at corners on a curved wall or a knife-edge tip (#851) (#935)
Rolling-ball blends now handle knife edges, where the two faces have opposite normals; an edge that runs out along an end face tangent to one side; and rounds that end in an earlier round on curved faces, picked together or one after another. The three edges at a vertex of smooth faces, rounded to one radius, end in a piece of sphere. When the given order of up to four edges is refused, or leaves a face boundary off its surface, other orders are tried.

Closes #851.

Co-authored-by: lasselian <22126420+lasselian@users.noreply.github.com>
2026-10-13 23:21:47 -04:00
NolyandNoly1990 45f92277cb i18n: complete Simplified Chinese catalog, Chinese font fallback and a coverage xtask (#930)
Adds a complete Simplified Chinese (zh) catalog covering every English message, a font fallback for Chinese text, and `cargo xtask i18n --check`, which checks that the complete catalogs (ja, zh) carry every English message and that every command label is translatable.

Co-authored-by: Noly1990 <20500931+Noly1990@users.noreply.github.com>
2026-10-13 23:00:39 -04:00
Irakli Kourtisandjyviko 587cf865b8 Display tessellation plans (4/4): one GPU dispatch for the scene, intersection edges by Newton (#806)
Packs the scene's planned bodies into one GPU upload and dispatch, and evaluates intersection edges by seeded Newton refinement from a fitted B-spline through exactly solved leader points, falling back to the previous evaluation near tangency. Intersection nodes carry their faces' parameters. This changes speed, not what the app draws.

Part 4 of 4 of #303.

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
2026-10-13 22:57:08 -04:00
Irakli Kourtisandjyviko c1c813e572 Thicken any surface, across sharp edges (#765) (#932)
Thicken now moves faces through the same mover as Offset Surface. Spheres, cones, tori and B-spline faces thicken as well as planes and cylinders, and a thickness that would fold a curved face over is refused. Where thickened faces meet at a sharp edge, a wall closes the gap and corners are closed with flat patches. Thickening into an inside corner is still refused with a clear message.

Closes #765.

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
2026-10-13 22:55:04 -04:00
bherbruck 7498e3b367 The model's body mesh comes from the reference mesher again (#940)
Fixes 20 action-matrix regressions from #805 and the seed 14 shell refusal (#897).
2026-10-13 22:37:11 -04:00
Igor Churmeev f037c92370 Add the solidcraft-checking skill (#880) 2026-10-13 22:37:07 -04:00
Irakli Kourtisandjyviko 2b70ed7b72 Offset Surface: faces copied out at a distance, exact or fitted (#758) (#872)
Offsets picked faces along their normals into a new surface body: planes and cylinders move exactly, other faces get a fitted B-spline within tolerance, and offsets that would fold over are refused. Adds the SurfaceOffset feature (noted under format 18), the surface.offset engine command and an Offset Surface dialog.

Closes #758.

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
2026-10-13 22:34:16 -04:00
bherbruckandClaude Opus 5.5 15b5b51bf5 Section Analysis in the browser, with an eye to turn it off (#822) (#947)
A section stayed on with nothing in the browser to switch it off; only the
viewport's context menu had Remove Section. While there is one, the browser
now has an Analysis folder listing it. Its eye (and the folder's) hides the
section and shows it again on the same plane; a double-click or Edit Section
Analysis opens the dialog, and Delete removes it. Browser rows with an eye
publish an `eye:<label>` handle for automation.

Fixes #822

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:18:06 -04:00
bherbruckandClaude Opus 5.5 abddd67d22 fix: a refused control connection reads what was sent before closing (#945)
Closing a socket with unread input resets the connection, and the reset could reach the client before the refusal did (requests_run_only_after_the_token failed 17 times in 100). The server now ends its side and drains, briefly and up to 64 KiB, what the client had sent before closing: 0 in 100.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:07:23 -04:00
bherbruckandClaude Opus 5.5 78d9aa0153 Booleans: small tools in large bodies are traced at their own scale (#559) (#946)
A through-all hole's tool is twice the largest body long, and the boolean's tolerances
followed that length: a 6 mm hole through a 920 mm part was traced at nearly 1 mm, wider than
the hole, and the cut came out empty or failed. When every other strategy has failed, the
boolean is now asked again (unshifted, then once shifted) at tolerances scaled to where the
bodies meet: the overlap of their boxes and the thinner operand's thickness. Its result is
checked as the others are and held to the retry guard. It runs last, so whatever an earlier
strategy answered is kept as it was, and only for single-shell operands (bodies in several
pieces are retried a piece at a time, each with this pass of its own).

The sampled shortcut also gave up on a slender tool lying across its box (a long hole drilled at
an angle fills little of it): it now samples past the first 2000 points until enough land
inside, so a tool that misses a body leaves that body as it is. Bodies that had enough samples
in the first 2000 are sampled exactly as before.

Baseline: the 90 actions that newly pass, rechecked one by one with the final build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:54:09 -04:00
Mateusz Sych 17db109c5d URDF export for robot simulators (#719) (#942)
A design with components and joints exports as a URDF robot description (file.export to .urdf, the MCP export tool, solidcraft-cli run --out, File > Export URDF...). It writes one binary STL per component in metres, links with mass, centre of mass and inertia from material density, and maps rigid, revolute, slider, cylindrical, pin-slot, planar and ball joints to URDF joints or short chains. Loops, broken joints and loose parts are reported as warnings. Options: mesh_prefix, effort, velocity. Docs in docs/urdf.md.
2026-10-13 21:52:30 -04:00
Irakli Kourtis d2e409f40c Surface walls named after their sketch curves (#757) (#850)
Each wall of a surface built from sketch curves (Surface Extrude, Revolve, Sweep and Loft) is now named after the curve it comes from, exactly rather than from geometry, so references to those faces survive edits.
2026-10-13 21:47:24 -04:00
bherbruckandClaude Opus 5.5 8746376ee9 Web: fetch the CJK font when Japanese text is drawn, whatever the UI language (#720) (#937)
The browser build fetched its Japanese fallback font only for a Japanese UI,
so a Japanese design name, file name or typed text showed as tofu with the UI
in English. After drawing each frame the web shell now looks through the
painted text for kana, CJK ideographs and full-width forms while no CJK font
is loaded, and fetches the same font once on the first hit. A failed fetch
with an English UI leaves the language alone and is not retried in a loop.
Sessions that never show such text download nothing extra.

Fixes #720

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:19:49 -04:00
bherbruckandClaude Opus 5.5 5808bcc0f0 Toolbar drop-downs show all their rows after a shorter list (#874) (#918)
* Toolbar drop-downs show all their rows after a shorter list (#874)

A panel's drop-down kept its size by an id made from the panel name alone,
so PLASTIC's MODIFY reused the one-row size of MESH's MODIFY and showed two
rows with a scroll bar. The id now includes the tab, and the rows' scroll area
may grow past the size the area remembered (up to the room below it), which
also covers the same panel collapsed and expanded.

Fixes #874

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

* Keep the Sketch Create menu's own sizing

Its rows sit in egui menus (also inside the overflow menu), where a minimum
height moved the rows between frames. Only the panel drop-downs need it.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:19:45 -04:00
bherbruckandClaude Opus 5.5 d23593d3eb Clamp the window surface to the device's largest texture (#869) (#905)
Moving a window between monitors of different scale can briefly ask for a
surface larger than the device's largest texture (11512 x 6808 against 8192 in
the report), and Surface::configure failed validation, which wgpu turns into a
panic. The surface is now clamped to the device limit with its aspect ratio
kept, and egui draws the whole window at the matching smaller scale, so the
frame is only softer until the size settles. The device also asks for the
adapter's own texture limit (16384 on most desktop GPUs) instead of 8192, and
the resize path and an unsupported surface log instead of unwrapping.

Fixes #869

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:19:40 -04:00
Irakli Kourtis 897b7d93b5 Surface Loft: sheets through open or closed sections (#847)
Surface Loft lofts open or closed curve chains, one section per sketch in pick order, into surface bodies. Adds the SurfaceLoft feature (format 18 note), the surface.loft command, a dialog with Edit Feature support, and kernel and engine tests.

Part of #757.
2026-10-13 21:07:16 -04:00
bherbruckandClaude Opus 5.5 7c85970bfc Fillet and chamfer: refuse a smooth seam as having no corner to round (#936)
* Fillet and chamfer: refuse a smooth seam as having no corner to round

Picking the seams where R10 rounds run into the faces beside them, and asking for an R1
fillet there, failed with "not supported yet: blending a smooth edge (... both faces next
to the edge must be planar)". That reads like a missing feature, but the faces meet
tangentially at those edges and there is nothing to round.

An edge whose two faces are tangent along it is now refused as an invalid pick that names
the edge and asks for the sharp edges.

Fixes #896.

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

* Robustness goldens: seed 72's smooth-seam fillet is now a rejected pick, not unsupported

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:02:06 -04:00
bherbruckandClaude Opus 5.5 8fb5742706 fix: imprinting a partial contact boxes faces by their control points (#929)
#825 chose the faces to split by boxing each face, dividing its curved edges down to the contact tolerance; on the sensor enclosure's lid that division never finished (press/pull and extrude on 18 faces grew past 24 GB and timed out in the action matrix, on CI as here). The boxes now come from the edges' control points, with no division; a face whose extent can't be found counts as near. A test holds the lid's press/pull to a time budget.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:52:35 -04:00
Irakli Kourtis aba57f1ca2 Surface Sweep: sheets carried from open curves and profiles along a path (#843)
Surface Sweep carries open curves and closed profile boundaries along a sketch path into surface bodies, with smooth walls along curved point paths. Adds the SurfaceSweep feature (format 18 note), the surface.sweep command, a dialog with Edit Feature support, and kernel and engine tests.

Part of #757.
2026-10-13 20:48:30 -04:00
Giovanni 57d738eac4 Join walls swept along splines in 2D (#908)
Walls swept along a spline have flat B-spline tops and bottoms, so booleans between them went to the general intersection, which fails on coincident spline faces. Such faces are now put on true planes, and two prisms of the same height and direction are decided in 2D with their spline outlines fitted by arcs within a micron. The result is kept only when its volume is plausible; otherwise the general boolean takes over. The free-form flag is cleared by a drop guard so an unwinding boolean can't leave it set.

Fixes #844.
2026-10-13 20:48:27 -04:00
selune 3f1c023563 Keep fine meshes whole at collapsed taper boundaries (#906)
The exact surface search could return an arbitrary parameter at a collapsed NURBS boundary, which corrupted the trimmed triangulation domain and lost volume on fine meshes of a fillet tapering to zero. Search results in the boundary projection are now validated, preferring a bounded parameter at a pole and snapping to a finite endpoint only when its transverse tangent has collapsed and the residual improves. Adds four regressions for decreasing chord tolerances, pole parameters, unbounded planar support and provisional preview.

Fixes #796.
2026-10-13 20:45:02 -04:00
Mqrius 7da7bbb335 Planar source matching looks only at planes with the same normal (#917)
Body::with_planar_sources compared every planar face of a result with every unsourced planar face of its inputs, so a boolean on an import with a few thousand facets ran out of its comparison budget before it started. The lists are now sorted by normal x and each face only compares with entries inside a narrow normal window found by binary search; the exact same_support test and the work budget are unchanged. Adds an engine test joining a pin onto a 4000-sided imported prism.
2026-10-13 20:44:58 -04:00
Irakli Kourtis 2eb292dd4a Surface Revolve: sheets turned from open curves and profiles (#824)
Surface Revolve turns open curves and closed profile boundaries about a sketch axis into surface bodies, with cone apexes and sphere poles where a chain ends on the axis, and a closed shell made solid. Adds the SurfaceRevolve feature (format 18 note), the surface.revolve command, a dialog with Edit Feature support and analytic kernel and engine tests.

Part of #757.
2026-10-13 20:30:41 -04:00
bherbruckandClaude Opus 5.5 b41ad23e3f Lip: run along the rim's opening, not the foot of a boss standing on the face (#933)
A lip picked on a face that isn't a shelled body's rim, such as the Sample Plate's top
face with its two holes and the boss standing on it, ran its band round the largest inner
edge. That was the boss's foot, so the band stood against the boss's wall and the join
failed with a boolean error about curved faces.

The band now goes round the face's opening (an inner edge with no material just over the
face inside it). A rim face that has a smaller hole in it still works. A face whose inner
edges are only where other features stand on it, or whose openings are alike in size (a
plate's holes), is refused with a plain message that names the rim face it needs.

Fixes #892.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:26:24 -04:00
bherbruck 2dcfea8df7 A generated many-part assembly: test and open-phase benchmark (#927)
Generates N parts of four primitive bodies, each moved into a placed component. The test (12 parts) checks the open, the world placement and the saved round trip; the ignored bench_generated_assembly prints parse, evaluation inside and outside the features, mesh warm-up and Measure.
2026-10-13 20:11:34 -04:00
bherbruckandClaude Opus 5.5 7135407268 Mesh a cylinder wall whose seam edges a cut split without fans across it (#921)
A boolean that cuts a pocket into the top of a cylinder (Shell does this) splits the
outer wall's straight seam edges at the pocket's floor. The wall's parameter grid has no
division along its rulings, so the triangulation fanned out from the two split vertices
to the far rims and its triangles cut through the part: the shelled Bolted Flange sample
drew and exported its rim cut off (mesh volume 29201 mm3 for a 40796 mm3 body).

Give the grid a line through each vertex that splits a boundary segment running along a
ruling, in a direction that needs no division of its own and within the grid budget.

Fixes #819.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:10:55 -04:00
Mateusz Sych 5f7ce80a93 MCP batch runs as one request in the app (#743) (#885)
The app's control protocol gets engine.batch: the steps run in one request (so one frame), each as engine.execute runs it, stopping at the first failure and rolling back to the checkpoint by default. The MCP batch tool sends it to a running app (with the control token), is never resent after a lost reply, and falls back to step-by-step when the app refuses (agent cursor shown, or a step loads a file). A batch may take up to an hour to answer.
2026-10-13 20:03:09 -04:00
Giovanni fdaf57daa3 Count the contact grid's own work, and test find_close_pair (#836)
find_close_pair's budget now counts the grid's own work too (each cell a triangle is filed under or visits), not only the pair tests, so a pathological pair of meshes can't spend unbounded time building and walking the grid. Adds tests for find_close_pair and the budget.
2026-10-13 20:03:05 -04:00
bherbruck 7eac1de9ee Open large designs faster: list each face's mesh vertices once when flagging seams (#914)
face_vertices lists each vertex once per face (first-met order) and normal_near measures each distance once; seam flags, merged topology and cone signatures are unchanged because the dropped repeats were identical entries.
2026-10-13 19:54:50 -04:00
Que fe07f79387 fix: a hole at sketch points drills into the part its sketch touches (#910)
Hole centres from a sketch now drill into the material on whichever side of the sketch plane it lies: material the spot starts in wins, then the nearest body ahead, else against the sketch normal as before. A sketch on a part's underside or below it now drills up into the part. Covers counterbores, through-all, edge and corner centres, patterns and reopening.
2026-10-13 19:53:21 -04:00
Irakli Kourtis b52d4390a0 face.delete heal: false leaves an open surface body (#861)
Delete Face takes heal (default true, as before). With heal: false the picked faces are removed and the gap stays open, leaving the remaining faces as a surface body (kernel remove_faces); deleting every face is refused. Saved only when false, noted under format 18.
2026-10-13 19:53:17 -04:00
bherbruck c616f50bd0 Open large designs faster: move bodies into components without copying the model (#915)
A body transfer works out the moved bodies first and puts them in place in the evaluator's own copy of the model, instead of copying every body of the model for each transfer.
2026-10-13 19:49:19 -04:00
bherbruck 6c61442bb2 Open large designs faster: meshes, face names and Measure on all cores (#916)
Display meshes after opening, the face names read when a feature finds a face by name, and inspect.measure over all bodies are now worked out per body on plain scoped threads (solidcraft_kernel::for_each_on_threads / map_on_threads), in body order and with the same results; the browser build stays serial.
2026-10-13 19:49:04 -04:00
bherbruck c333889fc0 Open large designs faster: index the previous bodies by name after each feature (#912)
After each feature the bodies before it are indexed by name once (first match first, as the linear searches were), feature components come from a map built once per evaluation, and Body::same compares shared face sources, paint and split lines by pointer first.
2026-10-13 19:45:18 -04:00
bherbruck 648f0ff768 Open large designs faster: count the cache budget without cloning wires (#911)
Body::retained_bytes counts face and edge uses on the borrowed boundaries (the same numbers, checked by a test), and each model body keeps the footprint measured when a feature made or changed it, so the evaluation cache's budget no longer walks every body's topology after every feature.
2026-10-13 19:44:46 -04:00
bherbruck eaab205302 Open large designs faster: name a moved body's faces only when asked (#913)
NameCell::same now refers to its source body and takes the source's names on first request, instead of naming every face of every moved or placed body up front. Names are unchanged.
2026-10-13 19:42:31 -04:00
Mqrius eea49922bd fix: face projection walks through pinch points (#899)
Where two boundaries of a projected face family touch at a single vertex, an even number of boundary edges meet there. The walk now stays on the face it came along (or takes the first edge in geometric order), so every loop closes; an odd count is still refused as a real branch. Adds a test with two squares meeting at a corner.
2026-10-13 19:27:47 -04:00
phanoob dd7790a43f Sketch text: bold and italic, text along a curve, alignment and spacing (#852)
A sketch text gets an optional style: bold and italic faces (a slanted fallback for italic), left/center/right alignment, letter and line spacing, and a path curve of the same sketch for the baseline to follow (reverse takes the other side). Edit Text gets B/I toggles, alignment, spacing fields and a Path chip, all live. The style is saved only when not plain and is noted under format 18.
2026-10-13 19:27:35 -04:00
bherbruckandClaude Opus 5.5 fe3717f2e4 fix: control port needs a per-launch token; MCP tool annotations tell the whole truth (#205) (#901)
solidcraft --control PORT writes a random per-launch token to a user-only file (<data>/control/PORT.token; 0700/0600 on Unix, icacls on Windows; SOLIDCRAFT_CONTROL_TOKEN overrides) and refuses any connection whose first request lacks it. mcp --connect, the demo and packaging smoke clients send it. MCP execute/batch/open/save/export/screenshot are annotated destructive and open-world, new_design destructive. Docs: control-protocol.md, mcp.md, AGENTS.md, pending release note in releasing.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:51:19 -04:00
bherbruckandClaude Opus 5.5 d9237d004e ci: the nightly and Run workflow get their own concurrency group (#877)
Only one run per concurrency group can wait. On a busy main each push replaced the waiting run, so the nightly and Run workflow runs on main were cancelled before they started. The group now includes the event.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:49:38 -04:00
JIJINBEI d8d54ded0d Renaming a body rewrites only the references to it (#875)
Renaming a body used to replace every string in later features equal to its old name, including sketch text, plane and feature names and expressions. Now only values under keys that name bodies (body, bodies, target, targets, tools) at any depth are rewritten, with a test that a sketch text spelling the body's name stays while a move follows the rename.
2026-10-13 18:47:10 -04:00
Igor Churmeev 95d394dcab Modeling skill: rim fillets after shell, slots along Y, boolean retry caveats (#879)
Corrects the modeling skill: an edge of the face a shell removes is rounded after the shell, a slot along Y is dimensioned horizontally, long shallow arcs measure to about 0.1%, and a boolean retry warning can hide wrong geometry when the volume does not match.
2026-10-13 18:46:47 -04:00
lunar-me 7ee351dc91 docs: perf.md grammar (one of the last features) (#895)
Fixes a plural in docs/perf.md.
2026-10-13 18:46:43 -04:00
lunar-me 9297f81501 docs: sheet metal guide grammar (corner relief size) (#894)
Says the rectangular relief is the size of the rule's corner relief.
2026-10-13 18:46:40 -04:00
lunar-me 2afc10fbd5 docs: sheet metal guide grammar (the base left beside a bend) (#893)
Adds a missing article in the sheet metal guide.
2026-10-13 18:46:37 -04:00
NOWN-DEV 5c98c1e24a A hole square to a folded sheet metal panel is flat machining (#656)
A hole drilled square to a flange's panel, not only the base's, is now a flat cut-out of the sheet: it unfolds with its panel to where the bend allowance puts it, shows in the flat pattern and DXF as a circle, and survives unfold and refold. A pocket tool spans the cut-outs already inside it (a counterbore's hole), so the order they are made in no longer matters. Fixes #560.
2026-10-13 18:46:11 -04:00
bherbruck 0040ce6bf2 Robustness goldens: record the seeds the 13 October kernel merges changed (#898)
Main has failed the full robustness goldens since #838, #839, #841, #855 and #856 landed together. Records the eleven seeds that now build differently (2, 6, 14, 22, 36, 43, 50, 63, 71, 73, 94), with a note. Shell now refusing seed 14, which used to build, is tracked in #897.
2026-10-13 18:46:00 -04:00
phanoob 1f7f37d1ef Cut an extrude's profiles that stand apart in one boolean (#848)
When an extrude cuts with profiles whose outlines stay more than 1 µm apart (the letters of a text), their tools are apart too, so they are joined and cut from the body in one boolean instead of one per profile. A ten-letter text cut drops from about 13 s to 1.5 s with the same result. Sheet metal targets and touching profiles keep cutting tool by tool. Tapered extrudes are unaffected.
2026-10-13 18:29:49 -04:00
Kevin Ravensberg 96f6424aec fix: confine MCP agents' files to allowed folders; recovery discard deletes only recovery entries (#205) (#578)
Agent tool calls (MCP, and bridged control requests carrying allow_paths) reach files only in the open design's folder (strictly inside home or the temporary folder, not in a hidden folder, and never its hidden files), the app's recovery folder, the server's private scratch folder and each --allow-path; --allow-any-path lifts the limit. Paths are resolved first, so .. and symlinks that lead out (including a save through a link, #845) are refused. On Windows, UNC, device, NT, verbatim non-drive, drive-relative, alternate-stream, reserved-device and 8.3 short-name paths are refused before any file system call, and the resolved path must be on a local drive. doc.recovery_discard and doc.recover {discard} delete only real recovery entries. Docs: mcp.md, control-protocol.md, README.
2026-10-13 18:24:08 -04:00
bherbruck 3d3f9b626f Robustness goldens: record seed 0 after the tessellation plans (#870)
#805 and the goldens (#826) landed side by side, so seed 0 was recorded before the new meshing reached main.
2026-10-13 17:30:16 -04:00
bherbruck c3ebe13991 Cargo.lock: solidcraft-gpu at the workspace version (#857)
Refreshes the solidcraft-gpu lock entry so --locked builds pass on main.
2026-10-13 16:48:43 -04:00
Irakli Kourtis b69cd2d0b7 feat: add and remove fillet spline points; handles on every edge (#855)
Double-click a fillet's radius curve to add a control point, or a handle to remove one; a spline fillet shows handles on all its edges.
2026-10-13 16:45:42 -04:00
Irakli Kourtis 753b0a2eab Unstitch and Reverse Normal (#856)
Unstitch takes a body's faces out as surface bodies; Reverse Normal turns a surface body's faces over. Both are saved in format 18. Closes #766.
2026-10-13 16:45:31 -04:00
bherbruck b702f0efde Fix live hinge animation and Drive Joint slider previews (#835)
Joint previews keep showing finished poses while the slider moves, and the latest value is still computed.
2026-10-13 16:34:09 -04:00
William Dunn 152418b624 A tool window for the circular sketch pattern (#801)
Circular sketch pattern gets a tool window with entities, center, quantity and total angle, replacing the click flow.
2026-10-13 16:34:00 -04:00
selune e99b3bd4e8 render: bounded cooperative HDR input foundation (#837)
A cancellable, bounded Radiance HDR decoder for render lighting input; reference images still accept PNG and JPEG only.
2026-10-13 16:33:51 -04:00
NOWN-DEV 8319c35a32 fix: a base trimmed for an edge flange keeps the arcs of its outline (#839)
A base trimmed for an edge flange keeps the arcs of its outline instead of falling back to a polygon.
2026-10-13 16:33:35 -04:00
froglemonade2 c3e0ea498a fix: joins, cuts and sketch profiles where outlines touch at a point (#841)
Fixes #588.
2026-10-13 16:33:24 -04:00
Mqrius b6e3a6c366 fix: contact and shell-grouping checks use a shared broad phase (#838)
Contact and shell-grouping checks find close triangle pairs through one shared grid instead of testing every pair. Refs #232.
2026-10-13 16:33:14 -04:00
Lasse Lian 4d8c80a86d fix: saving follows symbolic links and keeps the file's permissions (#845)
A save through a symbolic link replaces the file it points to and keeps the link; the saved file and its backup keep the original permissions.
2026-10-13 16:32:55 -04:00
phanoob ec5d7f9789 Sketch text: live Edit Text from the Text tool, a font list, handles, and whole-text picking (#821)
Live Edit Text from the Text tool, a font list, handles, and picking the whole text.
2026-10-13 16:29:37 -04:00
HiroKenEz 69050c81d9 Fillet and chamfer: refuse an edge pick far from every edge (#820)
Fixes #783.
2026-10-13 16:29:33 -04:00
William Dunn 53238fba5f Sketch Offset on two sides, or symmetric about the source (#807)
Sketch Offset can place the copy on both sides of the source, or symmetric about it.
2026-10-13 16:29:24 -04:00
Irakli Kourtis 5d661fa2af Export 3MF with B-rep face tags (#834)
3MF export carries each triangle's B-rep face tag.
2026-10-13 16:29:09 -04:00
Irakli Kourtis ff7a347013 Display tessellation plans (3/4): display meshes from plans, GPU evaluation in the viewport (#805)
Display meshes are built from tessellation plans, with GPU evaluation in the viewport.
2026-10-13 16:27:16 -04:00
bherbruckandClaude Opus 5.5 534393ed5a test: seeded goldens for the robustness seeds (#826)
tests/robustness/goldens.json records what each of seeds 0-95 builds: its steps, how it ended (ok, or the kind and the start of the error it was refused with) and, for the design it left, the bodies' total volume and area (within a relative 1e-6) and their face, edge and shell counts. tests::robustness_goldens fails on any change; ROBUSTNESS_GOLDENS_BLESS="why" records one, with the reason and the seeds it moved. Pull requests that touch crates/kernel run every 8th seed; pushes to main and the nightly run every seed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:23:27 -04:00
bherbruckandClaude Opus 5.5 200a83a629 Release 0.9.0 (#832)
Bumps the workspace to 0.9.0 and writes the release notes. Minor bump: designs are now format 17, and there are many new features.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:47:06 -04:00
William Dunn d302aa1a00 Construction Geometries, a Sketch Palette row that hides construction geometry (#804)
A Sketch Palette toggle (sketch.options show_construction) that hides construction geometry from drawing and picking; saved with the sketch's view options under format 17.

Co-authored-by: William Dunn <william.dunn@quasarke.com>
2026-10-13 15:15:13 -04:00
NOWN-DEV 9184994721 fix: coincident faces are pushed apart only where that changes nothing (#817)
A coincident face is pushed apart only when the space it moves through holds nothing that changes the result; otherwise the boolean falls through to the exact strategies.

Co-authored-by: NOWN-DEV <257771410+NOWN-DEV@users.noreply.github.com>
2026-10-13 15:10:31 -04:00
bherbruckandClaude Opus 5.5 d2676da789 perf: imprint partial face contacts only near the contact (#825)
The partial-contact imprint (#704, #751) split every face in the contact plane against every tool face, one call per face, and measured all the tool's cylinders for each pair: the sensor enclosure's Case shell went from 6.6 s to 76 s and timed out in the CI matrix. Only faces near the contact are split now, each surface once, in one call: 7.0 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:06:55 -04:00
bherbruckandClaude Opus 5.5 6a6a7e2865 test: give an action-matrix design 300 s to load (#812)
Loading a design (from a worker's start or restart to its first output) gets 300 s; each action keeps 90 s. oracle/41-tube-radial-holes (about 23 s here) timed out loading on a loaded CI runner, and reloads after a hung action were charged to the next action.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:59:36 -04:00
William DunnandClaude Opus 5 3ea8562c5e A tool window for the rectangular sketch pattern (#799)
sketch.pattern.rectangular gets a tool window with both quantities and spacings and an optional direction, instead of a click flow with a fixed count.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-10-13 14:54:48 -04:00
Irakli KourtisandClaude Opus 5.5 e540832626 Surface Extrude: sheets from open curves and profile walls (#815)
surface.extrude sweeps open curve chains and profile boundaries along the sketch normal into a surface body (walls only, no caps). Saved under format 17.

Refs #757

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:54:38 -04:00
Irakli KourtisandClaude Opus 5.5 8dd583930e Display tessellation plans (2/4): size rules and the facet angle (#803)
Per-face target side lengths, the caller's facet angle, refined grid lines, and fixes for underflowing coordinates and straight edges ending exactly on their vertex. Nothing drawn changes yet.

Refs #303

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:45:54 -04:00
HiroKenEzandClaude Opus 5.5 9bb18842e9 Convert Mesh says how big a mesh is, the limit, and what converts (#797)
A mesh over the conversion budget is refused with its triangle (or vertex) count, the limit, and that only constant-section parts convert.

Refs #346

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:43:29 -04:00
Mateusz Sych 9cabc3532d STL export welds shared vertices like the 3MF (#816)
STL (binary and ASCII) now welds each body's export mesh with the same tolerance as 3MF, so filleted and drilled bodies are closed. The weld grid's neighbour lookup no longer overflows for bodies far from the origin.

Fixes #784
2026-10-13 14:39:20 -04:00
William Dunn bdcfe3ff1d One tool window for sketch Fillet and the three sketch Chamfers (#789)
Sketch Fillet and the three sketch Chamfers share one tool window, with its own icons, labels and a scenario.
2026-10-13 14:32:13 -04:00
semmelrindeandClaude Opus 5.5 dbfb689f6c Single-hole snapping takes only shown points and checks the face once per pick (#798)
Follow-up to #718: a face place snaps only to sketch points that are shown, and the face under the cursor is looked up once per pick.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:28:54 -04:00
Irakli KourtisandClaude Opus 5.5 17b4b9a32c Reference case for a fillet with a B-spline radius (#810)
Adds oracle case 81 (a box edge rounded at a clamped cubic B-spline radius) with analytic expected values, and passes spline fillets through recipes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:28:49 -04:00
Irakli KourtisandClaude Opus 5.5 c8d668108a Surface toolset: icons and planned commands (#808)
Icons for the surface toolset, and its planned commands registered and disabled with the issue that tracks each one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:28:38 -04:00
NOWN-DEVandClaude Opus 5.5 dd084ec9e1 Sheet metal: tear as a fourth corner relief shape (#809)
A sheet metal rule's corner relief shape can now be tear; saved under format 17 with a note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:28:28 -04:00
Irakli KourtisandClaude Opus 5.5 bfbf5f9432 Display tessellation plans (1/4): planner with shared edge nodes (#802)
Adds TessPlan and the kernel planner (shared edge nodes, structured and Coons grids, constrained Delaunay for trimmed faces) behind Body::display_plan. Nothing drawn changes yet.

Refs #303

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:27:44 -04:00
William Dunn facff1050e Put Move/Copy Sketch Entities on the toolbar, behind a window with distances, an angle and a copy flag (#735)
Sketch Move gets a toolbar placement in Sketch > Modify and a Move/Copy tool window with X and Y distances, an angle, an optional centre point and a Create Copy flag, replacing the context-menu route that ran the command with no parameters.
2026-10-13 13:48:13 -04:00
William Dunn de1479029d A sketch dialog and the Sketch Palette on screen at the same time (#791)
A command dialog opened while sketching no longer hides the Sketch Palette: the dialog docks at the left of the viewport and the palette keeps the right. A view too narrow for both keeps the dialog alone.
2026-10-13 13:41:46 -04:00
William Dunn 16766bdbc7 Edit Sketch in the viewport menu over a sketch curve (#795)
Right-clicking a curve or point of a sketch that isn't being edited now opens that sketch's own menu, with Edit Sketch, instead of the body menu. Solid geometry in the selection keeps the body menu.
2026-10-13 13:37:54 -04:00
William Dunn d4fd760b0d A Mirror window for sketch entities, holding both picks until OK (#734)
Sketch Mirror now opens a tool window with Entities and Mirror line inputs, so both picks stay visible and editable until OK, replacing the old click-then-right-click flow.
2026-10-13 13:29:26 -04:00
Mithril_MEX d437c21368 feat(i18n): complete Japanese localization and eliminate UI hardcoded strings (#753)
Completes the Japanese catalog (all commands, panels, dialogs and preferences) and routes the remaining hard-coded UI labels through the translation lookup. English stays the default.
2026-10-13 13:24:24 -04:00
Alexander Nutz a8b3b21fd0 Feature history on the left side, with contiguous folders (#449)
The feature history can sit in a panel on the left, with folders that hold one unbroken run of features, drag and drop as one undo step, and a resizable divider. Timeline folders save under format 17.
2026-10-13 13:01:31 -04:00
Mateusz Sych be55a49b04 docs: agent skills for modeling and editing through MCP (#728)
Two agent skills for modeling and editing through the MCP server, with a command reference and model-only screenshot examples.
2026-10-13 12:49:02 -04:00
selune 293d660d6c fix(kernel): preserve intentional Split Face lines in contact joins (#782)
Intentional Split Face lines survive a join with partial planar contact, so the far face stays in two pieces. Follow-up to #704 / #674.
2026-10-13 12:46:34 -04:00
William Dunn beca7eb126 A tool window for sketch Offset, with a typed Distance, Flip and Chain (#731)
Sketch Offset gets its own tool window with a typed Distance, Flip and Chain, as a variant of the sketch modify window.
2026-10-13 12:46:24 -04:00
bherbruckandClaude Opus 5.5 1a0100cdd9 test: record the action-matrix outcomes that changed on main (#781)
26 baseline actions that fail on main: 17 are the retry guard's refusals of geometry that used to delete a body or fill past an edge (#644), 9 vary between runs (#707). Load timeouts left as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:37:05 -04:00
Irakli Kourtis 5267c96ab2 feat: drag a fillet's radius spline by its handles on the edge (#752)
A spline fillet's radius can now be dragged by handles on the edge, and the spline radius saves under format 17.
2026-10-13 12:35:42 -04:00
semmelrinde 159f9913c1 fix(sketch): a side put onto a projected line keeps its closed profile (#635) (#780)
A side constrained onto a projected line keeps the sketch's closed profile. Fixes #635.
2026-10-13 12:35:33 -04:00
semmelrinde dfa9e094cd fix: a size worked out from a bare diameter keeps its unit when the design's units change (#716) (#779)
A size typed as a bare diameter now keeps the design's length unit, so it no longer changes when the design's units change. Fixes #716.
2026-10-13 12:35:25 -04:00
Irakli Kourtis 8cdefd4cd5 test: reference cases for variable, chord-length and tapered fillets (#776)
Four analytic reference cases for variable, to-zero, chord-length and taper-to-fit fillets, with the formulas in tools/oracle-analytic.
2026-10-13 12:35:16 -04:00
Mqrius 7b0a1b296c perf(kernel): a refused edge is not rolled again after blends far from it (#777)
A blend no longer re-rolls an edge it already refused unless a later blend came near it, so long curved chains stop paying for the same refusal over and over.
2026-10-13 12:35:09 -04:00
Irakli Kourtis 25a3dc501b docs(roadmap): list the proposed surface toolset under M8 (#773)
List the proposed surface toolset under M8 in the roadmap and link the scope discussion.
2026-10-13 12:35:02 -04:00
bherbruckandClaude Opus 5.5 a3dfbaa0b0 Booleans build the same body every time: face order no longer follows heap addresses (#748)
truck's face and edge IDs are their addresses, so walking maps keyed by them followed heap layout, and the ribbed lid's side-wall hole passed or failed by chance. The planar boolean's rebuild and glue now take truck's connected components in the shell's order, and the planar boolean's own maps are ordered. Healing's union-find keeps the lesser root and takes a run's ends in order of use. The face classification after an intersection orders each component's faces by the shell first. The lid now builds the same body on every run, with a test that builds it six times in one process. Its 0.5 mm side-wall holes, and one sensor enclosure hole of the same kind, now fail every time and are marked failing in the baseline.

Fixes #707.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 12:31:23 -04:00
NOWN-DEV 58af8accfa fix: a projected edge stays the edge that was picked when its body changes (#730)
A sketch's link to a projected edge now keeps the edge's persistent name and finds it by that name when the body changes, instead of taking whichever edge ends up nearest its point.
2026-10-13 12:06:13 -04:00
William Dunn 4a989f6e2d Three readable line type icons in the Sketch Palette, with construction orange moved to a caller contract (#747)
The Sketch Palette's line types are three drawn icons (solid, one gap, gap with a dot) instead of text glyphs, and the construction orange is now chosen by the caller (`fixed_accent` on the toolbar) rather than hardcoded in the icons.
2026-10-13 11:53:59 -04:00
William Dunn aae70053ee Let the conic tool take a typed Rho, and preview the curve it is about to draw (#736)
The conic tool takes a typed Rho in its canvas value box and previews the curve it's about to draw, with the tangent legs as a faint guide.
2026-10-13 11:47:28 -04:00
William Dunn 9b5e39844a A Sketch Scale window, replacing a toolbar button that only raised an engine error (#733)
Sketch Scale now opens a tool window with the entities, an optional base point and a typed factor, instead of running the command with no parameters and raising an engine error.
2026-10-13 11:46:00 -04:00
Irakli Kourtis 1d30e4e1d2 feat: fillet radius tapers along an edge to the room there (#749)
Adds `fit: taper` to Fillet: where a constant radius doesn't fit along a straight edge, it follows the room there as a smooth spline, down to zero where a face closes to a point. Taper to Fit is in the Fillet dialog, and the feature's warning says how far it tapered.
2026-10-13 11:45:33 -04:00
Diogo Soares Rodrigues 1a5e961362 feat(ui): sketch tool shortcuts open a sketch on the selected plane (#737)
Outside a sketch, a sketch tool's shortcut opens Create Sketch on the selected plane or planar face (or asks for one), then starts that tool in the new sketch.
2026-10-13 11:45:15 -04:00
William Dunn ff609bc320 Make SolveApp::start honour a command's enabled flag, and disable solid.move inside a sketch (#740)
`SolveApp::start` now honours a command's enabled flag, so shortcuts, the command palette and the control channel can't start a disabled command, and Move is disabled while a sketch is open.
2026-10-13 11:43:45 -04:00
Mateusz Sych 62a7f903f2 fix: MCP instructions name body_topology, not a list_edges tool (#729)
The MCP server instructions now point to `body_topology` for finding edge points (there is no `list_edges` tool), with a test that every tool the instructions name exists.
2026-10-13 11:38:54 -04:00
bherbruckandClaude Opus 5.5 11c03fb0ab fix: imprint each surface once when fusing partial face contacts (#751)
Robustness seed 16 failed in most runs: the imprint split a block's side along both halves of a cylinder's round side, and the second split re-traced the first circle, sometimes leaving a sliver the glue could not match. The imprint now splits along each surface once (10/10 runs pass, 3/12 before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 11:35:27 -04:00
William Dunn 6f4a9d96fb Give the Move dialog the canvas value box every other dialog already has (#739)
The Move dialog gets the same canvas value box as the other dialogs: X for translating moves, the angle for Rotate, placed clear of the triad.
2026-10-13 11:28:50 -04:00
William Dunn 4112189551 A Feature Options section in the Sketch Palette showing the running tool's modes (#750)
Adds a Feature Options section to the Sketch Palette with the running tool's modes as icon buttons, so you can switch between e.g. circle or rectangle variants mid-tool.
2026-10-13 11:28:37 -04:00
SahenandClaude Opus 5.5 2bb24d1d2a perf: prism pairs before the mesh, bodies measured once, and a STEP import that copies less (#714)
A cut or an intersection between prisms (a plate and the tools through it) is now decided in 2D before either operand is meshed. A body's size and volume are measured once, a placement reads its orientation from its matrix, the evaluation cache is bounded to 64 MiB, and STEP import copies fewer shells. Hole plates evaluate 20-30% faster with less memory, and models are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:55:56 -04:00
NOWN-DEVandClaude Opus 5.5 a1cb8d4d36 feat: Convert to Sheet Metal takes a solid or a shelled body by its bend edges (#725)
Convert to Sheet Metal now takes a solid or a shelled body by its bend edges (`bends`): the faces reached from the fixed face across those edges become the sheet, keeping the part's envelope, with the flat pattern by the rule's K-factor and radius. Other edges between sheet faces are cut open. The result works with edge flanges, cuts, Unfold/Refold, the flat pattern and DXF. A conversion without `bends` is the plate conversion as before. The optional `bends` field is noted under format 17. Fixes #647.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:52:28 -04:00
MqriusandClaude Opus 5.5 a513304917 fix(engine): pick the face of a named body where bodies meet flush (face_body) (#710)
Extrude and Revolve from a body face take `face_body` to say whose face is meant where faces of several bodies meet flush at the picked point. Without it, an ambiguous pick now names the bodies that meet there instead of saying there is no face. Fixes #703.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:51:57 -04:00
semmelrindeandClaude Opus 5.5 d7f5b2435e fix(ui): the wheel over a dialog's list scrolls only the list (#678) (#713)
The mouse wheel over a dialog's list, a drop-down or a menu that hangs over the model now scrolls only that list; the view zooms only when the pointer is really over the model. Fixes #678.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:44:29 -04:00
semmelrindeandClaude Opus 5.5 ce5762d873 fix(ui): Box, Cylinder, Sphere, Torus and Coil dialogs show the unit of their sizes (#630) (#709)
The Box, Cylinder, Sphere, Torus and Coil dialogs now start their sizes in the design's units with the unit written out ("20 mm", "20 in"), like the other dialogs. OK makes the same sizes as before. Fixes #630.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:44:10 -04:00
semmelrindeandClaude Opus 5.5 831fa34752 fix(ui): a Single hole picked at a sketch circle's centre goes to the centre (#657) (#718)
A Single hole picked on or near a sketch point that lies on the face (a circle's centre) now goes to that point, as a Multiple pick does. Picks away from sketch points, or near one that isn't on the face under the cursor, stay where they were clicked. Fixes #657.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:43:47 -04:00
selune 11084a5963 fix(sketch): move and copy editable text as a whole source (#715)
Dragging a text outline now moves the whole editable text, and Move/Copy shifts or rotates it as one piece (a copy stays independently editable). Content, font, height and outline identities are kept; a drag is one undo step and Esc restores the start.
2026-10-13 10:29:02 -04:00
semmelrindeandClaude Opus 5.5 2c4d197bf4 fix(ui): Extrude and Revolve leave Profiles empty when the last sketch is already used (#684) (#705)
With nothing selected, Extrude and Revolve no longer pre-pick the last sketch's profile when a feature already builds from it, so OK no longer cuts into a part nobody picked. Fixes #684.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:28:17 -04:00
NOWN-DEV 4ff8f3e14d feat: free-form cut-outs stay exact in sheet-metal flat patterns (#692)
Sheet-metal cut-outs and base flange outlines drawn with ellipses, splines or text stay exact curves in the flat pattern and the unfolded body; the folded body follows them with tangent arcs within 0.001 mm (#659).
2026-10-13 10:18:15 -04:00
Irakli Kourtis eac4d66adc feat: variable fillet radius may run down to zero at one end (#675)
A variable fillet's radius may run down to zero at one end, where the round closes to the corner.
2026-10-13 10:17:55 -04:00
Lasse Lian cb071bb93b fix: axis or tangent plane on a drill tip no longer fails with "no face at NaN" (#567) (#655)
The nearest point on a triangle with a zero-length edge (a cone's tip) is no longer NaN, so an axis or tangent plane on a drill tip works again (#567).
2026-10-13 10:17:49 -04:00
selune 0e500cacd3 Render: add backend capability discovery and strict request resolution (#650)
Adds render.backends, a metadata-only command that lists the render backends, their executors and profiles, with strict request resolution in the render crate (docs/render-backends.md).
2026-10-13 10:17:41 -04:00
bherbruckandClaude Opus 5.5 1e4387f3ea fix: cuts no longer split walls they don't touch (#670)
A cut built slab by slab (the exact prism boolean) sewed the result from horizontal
slabs and only joins merged the coplanar pieces back. So a floor vent cut through the
Raspberry Pi case split the long wall into strips at the ports' tops and bottoms
(87 faces instead of 79 at 32 mm), and at 24 mm the end opening split the end wall.

- Prism booleans merge coplanar neighbours for every operation (faces only: edges,
  curves and surfaces stay exact), keeping each face's construction source where all
  its pieces share it, Split Face lines and regularization protection. Painted or
  protected operands keep their authored pieces as before.
- The prism history now tells apart a wall's several edges with its caps (a wall
  with openings meets each opening's floor), so cuts beside existing openings keep
  exact history and faces keep their names.
- Tests: the Pi case's faces pinned after every feature at 24, at 24 changed to 32,
  and built at 32 (each outer side one face); random openings and bosses on a
  shelled case never split another side; every corpus design rebuilds a parameter
  edit like a fresh build and reverts exactly.
- Action matrix baseline: demo/pi-case re-blessed (fewer, merged faces; no
  regressions), demo/pi-case-0.8 recorded, and 88 new passes on
  regressions/lid_ribs_flush_with_window.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:14:52 -04:00
bherbruckandClaude Opus 5.5 9ba72e118b Sketch: accept redundant but consistent constraints (#687) (#699)
A clover of same-size rectangles drawn around a shared corner was refused as over-constrained.

Constraints that remove no degree of freedom but still hold (zero residual) are now kept and shown dimmed as redundant. Conflicting ones are still refused. A redundant driving dimension is refused with "already determined by ...". Typed sizes become driven (reference) dimensions. Drawing no longer adds Horizontal/Vertical to a side that is already implied through two coincident corners. The solver reports redundant constraints using a Jacobian row-dependence test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:12:55 -04:00
bherbruckandClaude Opus 5.5 add4a18961 fix: fuse bodies that touch back to back over part of a face (#679, #674) (#704)
Two L's extruded up and down from one 2 x 2 grid now join into one solid (the planar rebuild gives each fan of faces around a pinched vertex its own vertex copy), and a pad whose end lies half past the edge of the face it stands on fuses exactly: contact faces are imprinted with each other's outline and the common faces glued away, still held to the retry guard. Face splitting handles a line crossing an edge twice; glue matches differently parametrised curves and keeps several pieces apart. Robustness seed 16 now fuses with the exact volume.

Fixes #679, fixes #674.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 10:02:44 -04:00
bherbruckandClaude Opus 5.5 17e42e7b94 Action matrix: mark the ribbed lid's side-wall hole as failing while it varies between runs (#708)
The ratchet has no flaky marker, so regressions/lid_ribs_flush_with_window Lid/F11:+y face.hole is marked as failing until the boolean is made deterministic (#707).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:56:43 -04:00
NOWN-DEV 812669e861 fix: an edge flange runs along what cut-outs leave of its edge (#702)
An edge flange picked on an edge that cut-outs have partly removed is built along what is left of that edge (the stretch nearest the pick), so each side of a notch can get its own flange.
2026-10-13 09:53:53 -04:00
semmelrinde c166c64b8e test: measure a scaling pair again when the load of other test binaries spikes (#649)
The two scaling tests measure a pair again (up to three times, keeping each size's fastest run) when other test binaries slow one down.
2026-10-13 09:38:28 -04:00
bherbruckandClaude Opus 5.5 0b1e61504d File format 17 for qualified piece names and sheet metal rim names (#700)
0.8.0 shipped format 16. Lineage names of a piece qualified by the faces around it, and sheet face names of a rim (rim.<edge>) or a bend end (rim.bend0/1), now come under format 17. Format 16 designs need no transformation, and the version stamp makes 0.8.0 ask for an update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:34:20 -04:00
bherbruckandClaude Opus 5.5 7249623855 Home and Fit frame what is shown now; middle double-click goes Home (#701)
Home, Fit, the orbit pivot and the scene's depth range counted sketches the view doesn't draw (ones used by an extrude or revolve, ones in hidden components), so after a body moved away from its sketch Home framed the empty spot it came from. They now use the world-space bounds of what is shown now (view::visible_bounds / view::sketch_drawn, viewport::view_bounds). The pick-box cache holds its world state instead of its address. A middle-button double-click goes Home in every preset (Preferences › Navigation: Home, Fit or Off).

Fixes #689

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:33:55 -04:00
Karl Von Richter 7b34d57b25 feat(ui): offer a cube navigator alongside signed axes (#664)
Adds a View navigator preference (Navigation page) with an optional cube navigator alongside the signed axes, which stay the default (#165).
2026-10-13 09:28:52 -04:00
lucas-aguilar89 aa24d9f071 Pasted features act on the pasted bodies (#613) (#682)
Pasted features now act on the bodies pasted with them instead of the originals (#613).
2026-10-13 09:28:27 -04:00
semmelrinde 319d360c85 fix(ui): a refused OK's error clears once the dialog's inputs change (#641) (#666)
A dialog's error from a refused OK now clears once its inputs, picks or the design change (#641).
2026-10-13 09:28:15 -04:00
selune e10b0b7d22 fix(ui): edit sketch text from letter interiors (#694)
Edit Text now works from a letter's interior as well as its outline, from the tool and the context menu, and highlights the whole text while editing.
2026-10-13 09:28:05 -04:00
lucas-aguilar89 d1a773a684 Save changes prompt: arrow keys pick the button, Enter gives it (#683)
In the save-changes prompt, Left and Right pick a button and Enter gives it.
2026-10-13 09:27:46 -04:00
Jonas Wrede fe1e5f1b95 test: regression test for #623: blind bore into a joined boss fails to mesh (fails on v0.7.0, passes on main) (#654)
Adds a regression test for the blind bore into a joined boss (#623).
2026-10-13 09:27:36 -04:00
bherbruckandClaude Opus 5.5 1e18ca9a46 Sketch Dimension: D, click, type (#695)
As soon as a pick settles the dimension (one line, circle or arc, or the second point or line of a distance or angle), its value box takes the keyboard with the measured value selected. The text follows the cursor while typing and Enter places it there (or in its default place), as one undo step. A click before typing pins the text and keeps the box focused, a click on another entity makes a distance or angle, Tab hands back to placing first, Esc cancels. Letters go into the box, not to tool shortcuts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:23:41 -04:00
bherbruckandClaude Opus 5.5 5ef1dcba8e Sheet metal rims and holes, plastic features, lofts, raised pieces and merged walls take face attachments (#677)
Sheet metal rims and bend ends attach by name (<panel>:rim.<edge>, <flange>:rim.bend0/1), as do face profiles from sheet faces. A plain or counterbored Hole square to a panel is machined into the flat pattern. Boss, Lip, Rest and Snap Fit keep the body's history and name their own faces. Hole tools name their faces by the part they cut, and pattern copies as copies. A pattern's repeated fillets keep the body's history. Loft sides are named by their section curves. A piece Offset Face moved off its face's plane attaches by its own qualified name. Sides of a profile grown to overlap a flush body keep their sketch line's name, so walls a Join merged take a sketch (#681). Face sketch 75% -> 90%, offset plane 75% -> 90%, press-pull/extrude 64% -> 85% in the action matrix; baseline blessed.

Fixes #494, fixes #495, fixes #560, fixes #561, fixes #563, fixes #564, fixes #681.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:13:09 -04:00
bherbruckandClaude Opus 5.5 e30f3c9ebe fix: Keep the loading card's Cancel button still; deterministic Cancel test (#686)
The loading card was centred on its height, so Cancel moved when the progress bar or the saved preview appeared, and a click could miss it. That's what made the Cancel test flaky on slow runners. The card is now anchored at its top, Cancel sits in the heading row and the progress bar is always shown. Progress::hold_at lets the test hold a load at a feature boundary until it's cancelled.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 09:05:13 -04:00
bherbruckandClaude Opus 5.5 738d532ce6 README: add the SELENE rover by @0xlillith as a showcase (#690)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:55:12 -04:00
bherbruckandClaude Opus 5.5 434fbc2d73 test: seed 16's partial face contact is refused, not built wrong (#676)
Main was red since #577 and #644 landed together. Seed 16's pattern copy rests half past the block's edge; the join used to add 72 mm³ past the edge silently and is now correctly refused. The test records that; proper fusion is tracked separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:28:04 -04:00
bherbruckandClaude Opus 5.5 302dd7e343 README demo for 0.8: the Raspberry Pi 4 case again, with a rounded rim, engraved text and PETG (#671)
The `pi` story of examples/demo/enclosure_ui_mcp.py keeps the 0.6 flow and adds the rim
rounded in one pick (a tangent chain), "Pi 4" engraved in the floor in DejaVu Sans Bold, and a
PETG material defined in the design, with its mass in Properties, before the height change.
The 0.6 story stays as `pi-0.6` (it built demo/pi-case). The center rectangle now comes from
the Rectangle drop-down. The finished case is in the action matrix corpus as
demo/pi-case-0.8.

docs/screenshots/demo-0.8.gif (9.3 MB) and demo-0.8.mp4 (9.9 MB) replace the 0.6 files: 41 s
at 9x playback.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 08:23:27 -04:00
bherbruckandClaude Opus 5.5 d2086835c1 flatpak-site: publish a release from before the app id change as it is (#668)
The Pages deploy failed while the latest published release (0.7.0) still carries ai.storyteller.solvecraft: the bundle check only accepted the new id and exited without a message. Accept either id (both architectures must agree), mark the old refs end-of-life only once the new app is in the repository, show the published id in the install instructions, and name the failing command on any error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 07:53:10 -04:00
bherbruckandClaude Opus 5.5 f5ae72057d Release 0.8.0 (#648)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 07:33:35 -04:00
bherbruckandClaude Opus 5.5 29258767ce fix: refuse Boolean retries that change the part away from the tool (#616) (#644)
Joining ribs flush with a lid's window left a zero-thickness slit far from the rib (the planar fallback glued back only one of the faces its pieces shared) and displaced the tool on the other joins. Gluing now joins every shared face pair, coincident-face separation checks there is material behind a pushed face for every join, and any join or cut that needed a retry must be closed, keep volume monotonic and leave the part unchanged outside the tool's box, or it is refused with an error. Feature command replies carry the feature's warning.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 07:10:29 -04:00
bherbruckandClaude Opus 5.5 392fae1af4 fix: Keep the window draggable while a design opens (#643)
The loading box was a full-window modal whose backdrop covered the custom title bar. It is now a light overlay under the title bar, so dragging, snapping, resizing and the window buttons keep working during open, import and recovery. The timeline rebuild shows feature N of M, and Cancel or Esc stops it at the next feature (solidcraft_doc::progress). Recovery restore now runs off the window thread too. A control or MCP doc.open or import is answered once the file is in and doesn't block frames; edits sent meanwhile wait their turn. The web build still opens in place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:59:20 -04:00
bherbruckandClaude Opus 5.5 8be0f64f5a Fillet and chamfer of one edge ending on curved, slanted or several faces (#646)
A single edge's blend no longer needs a plane square to the edge at each end. Straight edges: each rail ends where it reaches the end face's surface, the side edges there are shortened or continued along their line or circle, and the blend ends where its cylinder or chamfer plane meets that surface (a fitted curve checked against both). A chamfer may end across two faces. Curved edges: at a slanted or curved end the rolling ball runs on past the end and the strip is cut back where it meets that face. A straight edge running smoothly into a bend or rounded corner is blended along its whole smooth chain. Rolling-ball fixes: smooth chains follow edges stored against their curve, the ball touches only the faces of the edges it rolls along, the seam normal at a joint comes from the face there, and each strip is fitted with neighbouring sections at its own spacing.

Action matrix: edge.fillet 83.6% -> 93.0%, edge.chamfer 82.6% -> 93.3% (697 new passes, no regressions); reference tests 76/76.

Fixes #556

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:43:13 -04:00
Alan16888 9f6fb735dd fix(ui): Select Other keeps its list readable on dense meshes (#636)
Select Other keeps at most 20 rows per kind, nearest first, so a dense spot (a modelled thread, an imported mesh) no longer lists thousands of rows. The title shows how many were under the cursor in all, and ui.selectOther reports it as total.
2026-10-13 06:41:18 -04:00
Jonas Wrede 46e21127dd feat: insert another design as a linked component (#596)
component.insert_design adds a component holding another design's solid bodies, linked to its file (stored relative to the assembly once saved). component.update_links reads the linked designs again, component.break_link keeps the bodies and forgets the file, and component.links lists them. Links are saved under format 16; an older build keeps the bodies and drops the link.

Fixes #533
2026-10-13 06:40:58 -04:00
NOWN-DEVandClaude Opus 5.5 90869732a3 fix: edge flanges trim the base along their own edge and relieve bends that end beside it (#634)
An edge flange whose edge ends at an inside corner of the base now trims only the strip under its own edge, so legs and stems past that corner stay. Where the bend ends beside more of the base, the rule's bend relief (relief_width, relief_depth) is cut into the base, or a note says why it was left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 06:39:06 -04:00
Kevin Ravensberg 24cff445ad fix: fuse a loft standing on a face instead of returning touching shells (#577)
Fixes #396. A union that would come back as shells touching over an area is rejected and the face-in-face case is fused exactly; line and point contact stay as before.
2026-10-13 06:04:41 -04:00
bherbruckandClaude Opus 5.5 bb60a95289 Through-all holes beside walls: a long thin tool keeps its outside out (#629)
Body::new no longer turns a long thin solid (a through-all hole's tool) inside out on the strength of a coarse mesh volume: it checks again on a fine mesh first. 79 more face.hole actions pass in the action matrix; the remaining no-result cuts stay under #559.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:39:48 -04:00
selune 8d58e3b9f9 feat(mcp): support compact single execute replies (#591)
MCP execute accepts verbose: false too, using the same compact reply as batch (#599), and the MCP docs gain a section on compact replies.

Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
2026-10-13 05:36:49 -04:00
Lasse Lian f0d80a1d7e fix: filleting beside a modeled thread no longer freezes the app (#618)
Fixes #602. Collinear-edge merging, piece naming and the dialog preview each work in one pass instead of quadratic loops; the issue's example goes from 86 s to 5.1 s.
2026-10-13 05:36:22 -04:00
Irakli Kourtis 0f0bb09858 feat: fit reduce lowers the radius only where it doesn't fit (#496)
Fillet fit: reduce lowers the radius only where it doesn't fit: picked edges are grouped where they can limit each other (sharing a face or vertex), and each group gets the requested radius where it fits, otherwise the largest that does. Refs #293.

Co-authored-by: Iraklis Kourtis <jyviko@gmail.com>
2026-10-13 05:27:03 -04:00
bherbruckandClaude Opus 5.5 6d5df4b9b3 Sketch on cut walls, chamfers, pattern copies, join-merged sides and split faces (#625)
A flat face that no construction certificate names on its own now attaches by its exact history name (a new lineage face certificate): cut walls and floors, chamfer faces, each pattern or mirror copy, side walls a Join merged, and the pieces a later feature split a face into. Construction certificates still come first; a seed keeps its certificate when it is patterned. Two flush extrudes of one outline joined end to end keep their exact history in the prism boolean. Face sketch 52% -> 71%, offset plane 52% -> 71%, press-pull/extrude from face 41% -> 60% in the action matrix; baseline blessed.

Fixes #491, fixes #492, fixes #493, fixes #557.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:26:29 -04:00
Jonas Wrede 27f20f97cb feat: view.apply restores a named view or camera; MCP screenshot takes a named view or camera (#528) (#595)
New view.apply command restores a named view, a standard view or any camera (view state, not an undo step); the desktop viewport follows it and the browser's Restore uses it. The MCP screenshot tool takes a named view or a camera. Un-ignores the #528 regression test. Fixes #528.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 05:22:52 -04:00
bherbruckandClaude Opus 5.5 e67cc0c747 Linux app id: ai.storyteller.solidcraft, with an end-of-life rebase from the old id (#619)
The Flatpak app id, desktop/metainfo/MIME file names, icon name and the window's Wayland app_id / X11 class are now ai.storyteller.solidcraft, the id the ArtCraft launcher expects.

Existing Flatpak installs move over: the Pages repo marks app/ai.storyteller.solvecraft end-of-life with a rebase to the new id, so flatpak update offers the switch and Flatpak carries ~/.var/app along. A new install next to the old one copies settings and autosaves on first launch through a read-only grant on the old data folder. The metainfo declares <replaces>/<provides>, the desktop entry X-Flatpak-RenamedFrom, and AppImages drop the menu entry an older AppImage installed. deb/rpm upgrades remove the old-named files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 05:20:58 -04:00
selune bcbfa50eac Add bounded portable Home thumbnails to design files (#518)
A save with cache: true also stores a small bounded PNG thumbnail (240 × 150, at most 256 KiB, keyed to the exact document) made from cached meshes only. Home uses it when its local picture is missing, without rebuilding the design; ordinary saves and downloads still omit it. Closes #303.

Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
2026-10-13 05:20:47 -04:00
NOWN-DEV 24974e6f37 fix: a dimension from linked geometry keeps its side when the source moves past it (#614)
A point dimensioned from linked geometry (projected, included or intersected) keeps its side when a parameter moves the link's source past it: the solve reads sides and turns from the stored state instead of from beside the moved line, both on recompute and when editing the sketch.

Co-authored-by: NOWN-DEV <257771410+NOWN-DEV@users.noreply.github.com>
2026-10-13 05:15:15 -04:00
Lasse Lianandlasselian bf962a9989 fix(ui): primitives go on the picked face or vertex (#580) (#598)
Box, Cylinder, Sphere, Torus and Coil go where a face or vertex was picked before starting them: centred on the point, standing on a planar face (a box on an axis-aligned face sits on it; cylinders and coils take the face normal as axis). The dialog shows the position with a button to place at the origin instead. Fixes #580.

Co-authored-by: lasselian <lassesorensen80@gmail.com>
2026-10-13 05:14:35 -04:00
Karl Von Richter 04cfb74c5e feat: Group sketch creation tools and remember variants per part (#589)
Sketch Create groups its drawing tools under Line, Rectangle, Circle, Arc, Slot, Polygon and Spline. Each family's main button starts the part's last-used variant and its caret lists the others; choices are per open part, kept across sketch sessions and tab switches, and never touch the design or undo history.

Co-authored-by: Karl Von Richter <karl@von-richter.net>
2026-10-13 04:49:28 -04:00
Jonas Wrede 14635e5d3a feat: user-defined physical materials stored in the design (#532) (#606)
A design can define its own physical materials (material.create, material.edit, material.delete): name, density and an optional look. They work everywhere built-in materials do: assignment, mass properties, colours and configuration cells. Stored in user_materials, an additive field under format 16. Un-ignores the #554 regression test. Fixes #532.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:47:23 -04:00
NOWN-DEV a2f31f683d feat: flanges on flanges follow their flange's end and close at corners (#601)
An edge flange on another flange's far edge (a return) runs along that edge from end to end, following the parent's closed or cut-back corner, and returns of one feature close toward each other at convex base corners, mitred the rule's gap apart. Bends stop square where the mitre allows. Fixes #593.

Co-authored-by: NOWN-DEV <257771410+NOWN-DEV@users.noreply.github.com>
2026-10-13 04:45:19 -04:00
NOWN-DEV 9089edfb71 fix: the bend table and bend lines follow what cut-outs leave of a bend (#607)
The bend table, flat pattern and DXF list a bend only where its centre line has material: a bend that cut-outs (or pockets that together go through the sheet) shorten keeps what is left, and one they split gets a row per piece, all with the bend's number.

Co-authored-by: NOWN-DEV <257771410+NOWN-DEV@users.noreply.github.com>
2026-10-13 04:32:45 -04:00
Jonas Wrede a4ca1b68db feat: compact MCP batch replies; feature replies name changed and removed bodies (#530) (#599)
MCP batch takes verbose: false for compact replies: feature steps leave out the full body list and sketch steps the ids they repeat outside created. Feature replies now also list changed_bodies and removed_bodies next to created_bodies, so a compact reply still shows every effect of a step. Un-ignores the #530 regression test. Fixes #530.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:32:39 -04:00
Jonas Wrede 94ab54d1f5 fix(sheet): a box or cylinder cut along the sheet normal is flat machining (#541) (#550)
A Box cut, or a Cylinder cut whose axis is the sheet's normal, machines a sheet metal body like the extrude cut of its footprint: through the sheet it's a cut-out (a cylinder an exact round one in the flat pattern and DXF), part way it's a pocket. Primitives at other angles or across a bend are still refused. Un-ignores the #541 regression test. Fixes #541.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:29:31 -04:00
Jonas Wrede 9e2a1eb993 fix: faces a sheet fold moves keep their names (#535) (#585)
Faces that a sheet fold or unfold moves keep their names: points on a moved panel are taken back through the panel they lie on to where the sheet held them before the feature, and named from the faces there. Un-ignores the #535 regression test. Fixes #535.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:28:27 -04:00
Jonas Wrede abff7a49bb fix: tapered extrude of profiles mixing lines and tangent arcs (#546) (#587)
Tapered extrudes accept profiles that mix lines and tangent arcs (slots, D-profiles, rounded rectangles): each arc keeps its centre and angles and its radius moves with the taper, so its side stays an exact cone; lines are offset and mitred. An arc meeting another edge at a corner is still refused. Un-ignores the #546 regression test. Fixes #546.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:28:21 -04:00
Jonas Wrede 651e8e0853 fix: draft a whole round wall from one pick (#547) (#603)
Draft extends the picked faces to every face on the same surface, so one pick on a cylinder's side (stored as two halves) drafts the whole round wall into a frustum. Un-ignores the #547 regression test and adds a kernel test for a round hole. Fixes #547.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:27:12 -04:00
Jonas Wrede 4a86ad5a7c fix: extend a cut or join tool only past an end that lies on a target face (#545, #544) (#609)
An extrude cut or join only extends its tool past an end that lies on a target face, and by less than half the way to the next face level, so a tool can no longer cut through a thin floor beyond an end that touches no face. Un-ignores the #545 and #544 regression tests. Fixes #545.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:26:07 -04:00
selune 5ad8b3966f fix(ui): preview actual polygon outlines while drawing (#583)
Inscribed, circumscribed and edge polygon tools preview their actual outline (side count and orientation) while drawing, with the current line type, instead of only the support circle. Preview work is bounded. Fixes #520.

Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
2026-10-13 04:26:00 -04:00
Jonas Wrede dc060f8b86 fix(sheet): refuse a fold's fixed point far above or below the sheet (#371) (#538)
sheet.fold tests its fixed point against the whole sheet body, so a point far above or below the sheet is refused atomically, while a point on any face (including a flange's leg) still works. A stored fold whose point ends up in the air recomputes, keeps its side and warns. Un-ignores the #371 regression test. Fixes #371.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:20:16 -04:00
Jonas Wrede 9c94e1b96d fix(sheet): flat pattern reports cut-outs exactly as circles, lines and arcs (#542) (#552)
sheet.flat_pattern reports every cut-out exactly in a new cutout_shapes list, parallel to cutouts: a round hole as a circle, other cut-outs as their lines, arcs and (for other curves) polylines. Pockets get the same. The DXF writer and the JSON share one circle test. Un-ignores the #542 regression test. Fixes #542.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:18:37 -04:00
selune 2aff1c5303 ci: scan dependency advisories nightly and update XML parser (#605)
A scheduled/manual CI job scans locked dependencies for advisories, bans and unknown sources with cargo-deny 0.20.2 (never on pull requests, contents: read, no secrets). The first scan flagged quick-xml 0.38.4 (RUSTSEC-2026-0194/0195), so the workspace moves to quick-xml 0.41. Fixes #468.

Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
2026-10-13 04:17:42 -04:00
Jonas Wrede 135a63f549 feat: save body and occurrence visibility with the design (#531) (#592)
Hidden bodies, occurrence placements, sketches and origin items are saved with the design and come back when it is reopened. Visibility is still not an undo step, but hiding or showing something now counts as an unsaved change. Additive field under format 16. Fixes #531.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:16:49 -04:00
bherbruckandClaude Opus 5.5 2a434d59c3 Picks on a moved or pasted component build where it is shown (#558) (#610)
Construction axes, points and planes resolve their picks against the model as their component sees it (in the root, every placement, pasted instances too), and a Hole picked on another component's body cuts that body. components-arm 355 -> 502/594 and joints-hinge 304/390 -> 371/408 in the action matrix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 04:12:24 -04:00
selune 083ddf7722 fix(ui): preserve construction tools and dashed feedback (#600)
Construction (X) keeps the active sketch tool, its picked points and inline values while switching line type, and drawing previews and hover show the same dashed pattern as committed construction and centre lines. Fixes #584 and #581.

Co-authored-by: lillith <35337584+0xlillith@users.noreply.github.com>
2026-10-13 04:07:48 -04:00
Jonas Wrede db02a18627 fix(sheet): sheet.flange refuses rule values instead of ignoring them (#570) (#594)
sheet.flange refuses the sheet values its rule owns (thickness, K-factor, bend radius, reliefs, hem gap) instead of silently ignoring them, and says to use a rule. Un-ignores the #570 regression test. Fixes #570.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:06:59 -04:00
Jonas Wrede c727e2d0c2 fix(sheet): keep a stored fold working when its fixed point drifts off the sheet (#370) (#537)
A stored fold whose fixed point drifts off the sheet after a parameter change keeps the side the point lies on and shows a warning instead of breaking the timeline. A new fold still refuses a fixed point off the sheet, atomically. Un-ignores the #370 regression test. Fixes #370.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:05:01 -04:00
Jonas Wrede f9c19ade16 fix: raising a pattern's count after a rename numbers the new copies after the others (#372) (#540)
A copy added by raising a body pattern's count after a rename is numbered after the highest stored copy, so it no longer takes the number the rename freed. Un-ignores the #372 regression test and adds one for undo/redo and save/reopen. Fixes #372.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 04:04:44 -04:00
Paarangat Jain 711e5508b5 Add a security policy that points to private vulnerability reporting (#526)
Co-authored-by: Paarangat Jain <44281208+pjrus@users.noreply.github.com>
2026-10-13 03:59:52 -04:00
selune 913583bfee Show a bounded native-cache preview before authoritative reconstruction (#463)
Refs #303. file.save / file.save_as can store a warm native geometry cache (cache: true, off by default) keyed to the document hash, app version and adapter revision. Opening shows a bounded preview from it until the rebuilt session replaces it. Any cache failure counts as a miss.
2026-10-13 02:54:01 -04:00
bherbruckandjonaswre 1abd989a67 test: land the remaining ignored regression tests from #548-#573 (#582)
Ignored regression tests for #530, #531, #541, #542, #545, #547 and #570, from #548, #549, #550, #552, #555, #573 and #572.

Co-authored-by: jonaswre <jonas.wrede@beyondit.gmbh>
2026-10-13 02:51:12 -04:00
Kevin Ravensberg b1740aeee5 ui.at picks an origin plane where a click selects it (#575)
ui.at {plane} now returns the middle of the origin plane's square, or else the middle of a quarter that a click actually picks, so scripted clicks on XY no longer start a sketch on XZ from iso-like views.
2026-10-13 02:42:45 -04:00
bherbruckandClaude Opus 5.5 736d9277b8 Action matrix: every ordinary action on every face, edge, vertex and body of the design corpus (#553)
apps/solidcraft-cli/tests/action_matrix.rs tries sketch, offset/angle/mid planes, press pull, extrude from a face, hole, draft, shell, fillet, chamfer, axes, points, planes, projection, move, copy, mirror and pattern on the final bodies of 104 designs (the old design files, the examples, the 76 reference recipes, the enclosure demos, the Pi case built through the UI, eight typical parts), through the commands and picks the desktop sends, each design in its own process, with an edit round on a sample of passes. tests/action_matrix/baseline.json is a ratchet: an action that passed there and fails now fails the test; newly passing ones are reported for cargo xtask action-matrix --bless. Every CI run checks a fixed sample; the nightly Action matrix job runs the whole matrix in four parts. 30,567 of 35,576 actions pass (85.9%); results and causes in docs/action-matrix.md, one ignored reproduction per open cause (#556-#568).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:41:17 -04:00
Jonas Wrede 9283d2b5bb feat: feature replies list the bodies the feature created (#525)
Fixes #374. Feature replies gain created_bodies: the bodies the new feature made, apart from the ones it changed or that were already there. Documented in the execute tool description and docs/mcp.md.
2026-10-13 02:39:19 -04:00
selune c1cd4cf194 fix(ui): persist keyboard zoom and stabilize scale drags (#574)
Fixes #478. Desktop Ctrl/Cmd +/-/0 now update the saved UI scale instead of flashing back, and the Preferences scale slider applies the new layout on release (range 50-250%, matching the preference validation). Addresses the drag jitter in #272.
2026-10-13 02:33:45 -04:00
Jonas Wrede 1a6ff0954e fix: inspect.interference prunes by bounding-box sweep instead of refusing more than 200 bodies (#536)
Fixes #527. Bodies are paired by a sort-and-sweep over their bounding boxes, and the cap moves from 200 bodies to 20,000 overlapping pairs per call (reported as truncated / skipped_pairs).
2026-10-13 02:29:45 -04:00
Jonas Wrede 88e9661457 test: failing regression test for #546 (ignored until fixed): solid.extrude with taper refuses profiles of lines and arcs: "not supported yet: tapered profiles mixing lines and arcs" (#571)
Adds an ignored regression test for #546; it runs once #546 is fixed.
2026-10-13 02:28:16 -04:00
Jonas Wrede 7a8754bef7 test: failing regression test for #544 (ignored until fixed): Blind Ø2 hole into a long D-profile bar is refused ("exact fallback … takes planar faces only") although it stays clear of the round face (#551)
Adds an ignored regression test for #544; it runs once #544 is fixed.
2026-10-13 02:28:12 -04:00
Jonas Wrede 6441621b8b test: failing regression test for #528 (ignored until fixed): No way to apply a saved named view or a camera from scripts/MCP (view.save stores views, screenshot only takes standard views) (#543)
Adds an ignored regression test for #528; it runs once #528 is fixed.
2026-10-13 02:28:08 -04:00
Jonas Wrede d7797c3d38 test: failing regression test for #535 (ignored until fixed): Sheet metal: hole faces on a folded panel lose their sketch-curve names (side:c1#1 becomes F6:face:10) (#539)
Adds an ignored regression test for #535; it runs once #535 is fixed.
2026-10-13 02:28:05 -04:00
Jonas Wrede b11841b0bc test: failing regression test for #532 (ignored until fixed): Physical materials: no user-defined materials (library is fixed to 14 entries) (#554)
Adds an ignored regression test for #532; it runs once #532 is fixed.
2026-10-13 02:28:01 -04:00
Jonas Wrede a8121215bc test: failing regression test for #533 (ignored until fixed): Assemblies: no way to insert another design as a linked component (STEP insert drops sheet-metal history and never updates) (#534)
Adds an ignored regression test for #533; it runs once #533 is fixed.
2026-10-13 02:27:58 -04:00
NOWN-DEVandClaude Opus 5.5 a69c2928f0 feat: sketch text takes a font, an installed family or a font file (#516)
sketch.text and sketch.edit_text take an optional font: an installed family or a .ttf/.otf/.ttc file. Edit Text gets a Font field. The font is kept with the text under format 16; no fonts are bundled, and the font search is bounded.

Fixes #513

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 02:04:59 -04:00
William Dunn 7d00df0a3f Give solid.move the copy half of its Move/Copy label (#498)
solid.move takes copy: true to keep the source bodies and transform a new copy of each, with a Create Copy box in the Move dialog. Designs without it save and load exactly as before.
2026-10-13 01:54:01 -04:00
NOWN-DEVandClaude Opus 5.5 69e54f85d2 fix: draw small radii on large bodies round (#515)
Display meshes also bound the angle one facet turns, so bends and small holes on a large body are drawn round instead of as chamfers or squares. Bounded per face, and only the display mesh changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 01:43:45 -04:00
William Dunn bf2a9f5541 Give the centerline tool a toolbar icon, and paint both line-type icons in construction orange (#427)
Centerline gets a place in SKETCH > CREATE with its own long-short icon, and both line-type icons now draw in the construction orange.
2026-10-13 01:26:47 -04:00
NOWN-DEVandClaude Opus 5.5 d6af554c6d feat: corner reliefs at sheet flange corners, chosen by the rule (#488)
Sheet Metal Rules get a Corner Relief Shape (notch, round or rectangular), sized by the rule's corner relief, cut where two bends meet at a corner of the base. Notch, the default, keeps existing designs as they were. File format 16.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:25:50 -04:00
NOWN-DEVandClaude Opus 5.5 c2a9c71436 fix: text extrudes as letters with their counters, alone or beside drawn curves (#502)
Text outlines count as drawn geometry when picking profiles, and "all profiles" leaves out the counters of letters (the holes of A, B, 8), so text extrudes as letters with their holes whether or not other curves are drawn next to it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:25:39 -04:00
MqriusandClaude Opus 5.5 f93577ad70 fix(doc): pieces of a body cut apart again get names of their own (#512)
When a body that was already cut into pieces is cut again, the new pieces are numbered past the names already taken ("Bar (2)"), instead of reusing "Bar (1)".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:25:28 -04:00
selune 9f88537927 Define a kernel-neutral native B-rep format and guarded adapter (#461)
A SolidCraft-owned portable B-rep schema (geometry schema 2 with analytic curves and surfaces, explicit frames and ranges, fitted intersection curves) and a versioned body envelope (SCBREP03) with its own metadata records, a bounded binary codec, a validator with resource budgets, and an adapter that refuses what it can't convert exactly. Pinned by a box-and-fillet golden fixture. Not yet written by application saves. Refs #303.
2026-10-13 00:13:29 -04:00
Scott BowdenandClaude Opus 5.5 3159fc0dd0 fix(kernel): a small overlap is not taken for bodies apart or inside (#484)
When sample points say two bodies are apart or one is inside the other, the facetings are now checked for surfaces passing through each other; if they do, the boolean is computed instead of shortcut. If it still can't be intersected, the sampled answer is kept as a flagged, unverified result, and Interference lists such pairs as "could not be checked" with the reason. Fixes #442 and #443.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:13:16 -04:00
NOWN-DEVandClaude Opus 5.5 340ac0313a fix: a hole placed on a face follows the face along its axis only, and warns when off the face (#510)
A hole positioned on a face now follows that face along the hole's own axis instead of sliding to the face's nearest point. When the face no longer reaches the position, the hole stays where it was placed and the feature says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:13:04 -04:00
MqriusandClaude Opus 5.5 bb431f7ac2 fix(sketch): merged adjacent profiles have whole edges where shared ones ended (#509)
When adjacent profiles are merged for one extrude, collinear lines on either side of where a shared boundary ended are joined back into one edge, so the body has no split faces or extra vertices for later booleans to trip on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:11:59 -04:00
NOWN-DEVandClaude Opus 5.5 2f7046efac fix: lines lying on one another do not cross in planar booleans (#507)
Two line segments that lie on one another within the tolerance, a hair from parallel, no longer report a crossing somewhere along their overlap; the ends of the overlap split the edges instead. This stops booleans from leaving sub-tolerance edges or stray vertices.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 00:11:55 -04:00
William Dunn aaeb7ceea7 Sketch Palette: a right-aligned control column, one Look At icon, a disclosure triangle, plain-noun labels (#497)
Every Options row puts its control in one right-aligned column, Look At is a single label with an icon button, Options folds with a disclosure triangle, and the toggles use plain nouns (with dedicated ids for Profile and Points so they don't collide with other labels).
2026-10-12 23:58:58 -04:00
William Dunn 10c6b865c7 Sketch Palette: Linetype tooltips say what the click does (#499)
The Linetype buttons now read "Draw as {linetype}" with nothing selected and "Convert the selection to {linetype}" with a selection, in all four locales.
2026-10-12 23:58:40 -04:00
bherbruckandClaude Opus 5.5 a9879fa668 Release 0.7.0 (#508)
Bump the workspace to 0.7.0 and write the 0.7.0 release notes. Minor bump: the design format moved to 15 (sketches on cylinder caps and sheet metal panels), so 0.6 can't open 0.7 saves, and new features landed (Select Other, per-edge flanges, sketches on caps and sheet metal faces).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:15:08 -04:00
bherbruckandClaude Opus 5.5 45bc22fc30 fix: faces of an extrude drawn over a face's projected edges keep an exact source (#505)
A profile traced over a face sketch's projected boundary left the extrude's caps and sides without an exact planar source, so a sketch or offset plane on the new top face was refused. The drawn curve is now the edge's source where it covers a projection. Clearer fixed-plane message; regression tests in the engine, UI harness and design corpus, plus a corpus-wide flat-face attachment check (known gaps under #491-#495).

Fixes #489

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:01:35 -04:00
bherbruckandClaude Opus 5.5 4aaa5457b3 feat(ui): Select Other — pick what lies under something else (#503)
Hold the left button still (long-press on touch) or right-click › Select Other… to list everything under the cursor: sketch points and curves ("Line 3", "Point (arc 2 centre)"), vertices, edges, faces, bodies, profiles, planes and axes, filtered like a click and by the open dialog's active input. Hovering a row lights it up; clicking selects it (Shift/Ctrl add) or feeds the dialog. A repeat click on the same spot with nothing changed selects the next thing there; a double-click keeps its own actions.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:56:57 -04:00
bherbruckandClaude Opus 5.5 1fd4a2f782 README demo for 0.6: a Raspberry Pi 4 case (#504)
Replace the 0.2.1 enclosure recording with a 40 s take of the current desktop UI building a Raspberry Pi 4 case: a sketch dimensioned with the board's parameters, extrude, fillets and shell, an M2.5 standoff patterned on the 58 x 49 mm hole rectangle, port cut-outs, floor vents, a colour, then height 24 -> 32 mm in Parameters. The demo script gains the pi story (now the default).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:56:53 -04:00
onenewcode 00baf1d883 fix: follow feature renames in references (#486)
Fixes #401
2026-10-12 22:39:13 -04:00
MqriusandClaude Opus 5.5 91de39d3fe fix(kernel): revolving adjacent profiles on the axis makes no zero-area faces (#490)
Profiles revolved together merge into one region that keeps a vertex where
each shared edge met the axis. With the axis edge in several pieces the loop
skipped the builders for a profile with an edge on the axis, and the generic
sweep turned each piece into a degenerate face that booleans failed on.
Runs of collinear axis lines are merged into one first.

Fixes #487

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:38:48 -04:00
bherbruckandClaude Opus 5.5 219816d282 Name untitled designs after their file (#483)
* fix: name an untitled design after its file on save and open

Design tabs all read "Untitled" because a design's name only ever came
from file.new. Saving a design still under the default name now names it
after the file (bracket.solidcraft -> "bracket"), and opening a file whose
stored name is the default shows its file name. Names chosen by the user
are kept. The rename is part of the save, so it is not an undo step and
does not leave the design marked as changed.

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

* test: expect the file's name after saving an untitled design

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:19:26 -04:00
MqriusandClaude Opus 5.5 2880525e6b fix(ui): the Combine dialog previews without an error, so OK is enabled (#481)
The live preview also evaluates each command as a new body, to show the tool of a join or cut
(as_new_body sets every `operation` to "new"). A combine has no new-body form: its operation is
between bodies that already exist, and solid.combine rejects "new" ("operation must be join, cut
or intersect"). That error became the preview's error even when the combine itself worked, and
since OK is greyed while the preview has an error, a combine could not be applied from its
dialog at all. as_new_body now leaves solid.combine alone, so its preview shows the result.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:50:05 -04:00
Irakli KourtisandClaude Opus 5.5 aa156f4a9f fix: report every fillet refusal of a radius too big as too large (#477)
`fit: reduce` acts only on refusals marked too large. Three kinds weren't:
the corner build's contact lines leaving their faces or running backwards,
and a curved chain's contact reaching past the end of the seam it trims. Two
more were hidden: a later edge's too-large refusal lost to an earlier edge's
other error, and the corner build's lost to the local blend's in `fillet`.
The messages are unchanged.

Refs #293

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:49:00 -04:00
Irakli KourtisandClaude Opus 5.5 ccc36dc1f9 fix: shell a body whose curved edge is rounded smaller than the wall (#476)
Moved in by more than its radius, a round collapses and its neighbours meet
along a new edge. That edge was always built as a straight line keyed by its
ends, so on a disc whose circular edge is rounded the four arcs where the floor
meets the side became one chord used by four faces, and the shell failed with
"This shell is not oriented and closed" (#319).

The new edge now runs where the edge's face meets the face across the round
(the round's far side, crossed as a strip), through the edge's middle moved
onto both: a line or a circular arc, checked to lie on both moved faces, and
refused otherwise. Twins are the new edges with the same ends through the same
middle.

Fixes #319

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:48:27 -04:00
bherbruckandClaude Opus 5.5 0fe032438f Note sheet metal panel anchors under format 15 (#482)
Claude-Session: https://claude.ai/code/session_01HWLPLy7ug7URSK5WybjRNa

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:41:58 -04:00
William Dunn 72c5d974b8 fix(ui): draw sketch constraint glyphs in red (#414)
* fix(ui): draw sketch constraint glyphs in red

Traditional CAD convention marks constraint glyphs in red. glyph_edge
and glyph_text in both themes now use red values. glyph_bg is
unchanged. The same glyph box switches to the error colour when a
constraint fails. The new reds are chosen to stay distinct from that
error colour. They are also distinct from the blue used for selection
and hover. See the pull request body for the before and after RGB
values and the contrast reasoning.

* fix(ui): match constraint toolbar icons to the red glyph colour

The CONSTRAINTS panel buttons and their drop-down rows drew their accent
strokes in the normal sketch tool green. They now use glyph_text, the same
red the matching constraint glyph box uses in the sketch view. A new
panel_accent helper in toolbar.rs picks the colour: the constraint red for
the CONSTRAINTS panel, the existing sketch green for every other sketch
tool, and the plain accent everywhere else. No other toolbar styling
changed.
2026-10-12 21:37:42 -04:00
Irakli KourtisandClaude Opus 5.5 201e9d5f9f Patch fills non-planar edge loops with one smooth face (#250) (#472)
* Add a one-surface fill of closed 3D loops to geom

A closed loop of curves that does not lie in a plane has no exact surface;
the usual answer is one smooth face fitted over it. fill_loop does that:

- the loop's best-fit plane (Newell normal), with a frame chosen from the
  loop itself so the result does not depend on where the loop starts, its
  direction, or where the part sits
- the loop seen along that normal must be simple and must not fold or run
  along the normal; otherwise it is refused ("not supported yet"), never
  filled with wrong geometry
- one non-rational bicubic B-spline height field over the loop's box,
  fitted by least squares to the loop's samples (The NURBS Book 9.4.1,
  9.4.3, eq. 9.62/9.65) with a thin-plate smoothness term
  lambda*A*int(h_uu^2 + 2 h_uv^2 + h_vv^2), A one grid cell's area, so the
  fit is the same at any scale; only planes are reproduced exactly
- span counts per direction (long narrow loops fit), refined until the
  loop is within tolerance, else refused with the deviation reached

bspline: bisected span search, basis functions on a stack array, one
clamping helper shared by curves and surfaces, one binomial helper.

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

* Patch fills non-planar loops of edges with one smooth face (#250)

surface.patch with edges refused any loop that is not planar ("not
supported yet: patching a loop that is not planar"). It now fills it:

- planar loops still get one exact planar face, but planarity is certified
  conservatively: lines by their ends, B-spline/NURBS edges by their control
  points, intersection curves by their samples lying on a support plane
  within tolerance; a loop that can't be certified is filled instead
- other loops get one face from geom's fill_loop, bounded by the body's own
  chained edges, whole (no split): its history is one generated face whose
  edges are the body's, and it stitches back into a closed solid
- intersection-curve edges are sampled by a seeded, guarded double
  projection shared by the planarity test and the fill (plane_fit)
- the face tolerance is 10x the planarity tolerance (documented: later
  operations take the face to contain its edges within it)
- edge picking runs inside the guard; the geom->truck conversion is shared
  with the T-spline sheets (spline_conv); the Newell plane fit is shared with
  STEP import

Tests: 3- to 8-sided, concave (dart, arrow, plus), flat-corner, two-edge and
imported rims fill as one face and stitch closed (volumes against an
independent integral); finite mesh normals, same result for any pick order
or placement; non-height-field loops and too-fine detail are refused.

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

* Test the Patch dialog filling a non-planar loop of picked edges

The Patch dialog's only test used a sketch profile. Picking the four
curved rim edges of a box cut by a sideways cylinder now makes one
surface.patch command whose result is a single smooth face over the rim.

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:37:01 -04:00
NOWN-DEVandClaude Opus 5.5 af0440aaab feat: sketches on a sheet metal face attach to its panel (#474)
A sketch on a flat face of a sheet metal body was refused ("this face has no exact planar
source"), so the only way was a plane frozen in space. When the sheet changes (a flange's
angle, the thickness), the frozen sketch stays behind: a cut from it fails on the slanted
flange or leaves a blind pocket in the thicker sheet.

`sketch.create` with a face of a sheet metal body now attaches the sketch to the panel the
face belongs to. It keeps the face's name, `sheet:<body>:<panel>:<top|bottom>` (the panel is
the base or a flange's feature tag), and takes the panel's exact plane from its flat region
and fold each time the model is evaluated, so the sketch follows the sheet through parameter
changes, unfold and refold. Its coordinates are the flat pattern's (the bottom face's y
mirrored). A face that is gone is reported as a lost attachment, never replaced by the
nearest face. The attachment follows a renamed or copied body, shows as `sheet_face` in the
command result and as its own status in Redefine Sketch Plane. `fixed: true` still freezes.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:36:58 -04:00
Irakli KourtisandClaude Opus 5.5 49956c2797 Sketch on a cylinder primitive's caps; format 15 (#460)
* Sketch on a cylinder primitive's caps (#420)

A solid.cylinder's caps carried no construction certificate, so a sketch
on its top or bottom was refused unless fixed:true (#420's crossed
cylinders, built from primitives).

- kernel: FaceSource::CylinderCap { producer, high, plane };
  Body::with_cylinder_certificate turns a cylinder's two extrude caps into
  certified caps. kernel::cylinder's output is unchanged for its other
  callers.
- doc: FaceCertificate::CylinderCap { high }, named F<id>:top / F<id>:bottom
  as the cylinder's face names already are; New Body and Join cylinders
  are certified, shells carry it as their planar source.
- format 15: designs can store the new certificate kind; version 14
  designs need no change, and older builds ask for an update instead of
  meeting an unknown kind.

Tests: top and bottom attachments follow a height and base edit and a
reopen; crossed joined cylinders keep both caps attachable; a shelled
cylinder's floor keeps its cap identity; malformed certificate JSON is
refused.

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

* Format 15 also covers sketch_axis links (#448)

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:32:07 -04:00
selune b24e478bc1 Read design metadata and previews through a bounded seekable archive (#462) 2026-10-12 21:27:49 -04:00
NOWN-DEVandClaude Opus 5.5 9ab25cfd4a fix: sketches on planes built from the model can project, intersect, include and hold text (#459)
Project, Intersect, Include 3D Geometry and Text resolved the active sketch's
plane without the model, so on a construction plane that is built from geometry
(perpendicular to a plane through a sketch line, through three points, midway
between two faces, tangent, along a path) they stopped with "this plane is built
from the model: evaluate first". Evaluation resolves the same plane against the
model before the sketch, in its component's frame; the sketch commands now do
the same, in one place, for adding a link, refreshing the links on an edit,
snapping a point onto an edge and the internal face profiles.

Tests: a sketch on a plane through a sloped sketch line projects a point and a
line of that sketch, follows a parameter that tilts the plane (also after reopen
and after another edit), holds text, intersects a body and includes an edge; the
same on a three-point plane and a midplane.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:27:28 -04:00
Irakli KourtisandClaude Opus 5.5 1ae6e9beb4 Ctrl+left drag on empty space orbits in every navigation preset (#471)
A left press with Ctrl (or Cmd) held, no Shift or Alt, on empty space orbits
for that press, unless the preset binds Ctrl+left itself. It only orbits
once the pointer moves: a Ctrl+click still adds to the selection, and
Ctrl+drag on sketch geometry still drags it without snapping. Works with a
tool active, without adding geometry. Ctrl+box selection from empty space
becomes the orbit; Shift+box selection still adds.

Preferences › Navigation and the UI tour mention it. Test drives every
preset through real pointer events.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:27:20 -04:00
Irakli KourtisandClaude Opus 5.5 fc1d70cc3b docs: describe the sketch solver as it is (#475)
The README called the solver damped Gauss–Newton. It now places what the
constraints fix by construction (loci intersections and rigid-cluster
decomposition, after Owen 1991 and Fudos–Hoffmann 1997) and runs
Levenberg–Marquardt on the rest, with the rank of its Jacobian giving the
remaining degrees of freedom. The constraint list also gains the ones it was
missing (point on curve, curvature, point-to-line, arc sweep, arc length, slot
length), and live drags are described.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:26:54 -04:00
bherbruckandClaude Opus 5.5 dd67472252 Orbit from empty space turns around the visible geometry (#464)
With "Orbit around: Point under the cursor", a drag that starts over
empty space used to pivot on the point under the cursor at the target's
depth, so an orbit begun near the edge of the view swung the model
around an off-screen point. It now pivots on the centre of the visible
bodies and sketches (the same bounds Fit frames), or on the view centre
when nothing is visible. A drag that starts on the model is unchanged.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:01:49 -04:00
bherbruckandClaude Opus 5.5 9062c739d8 docs: link preview cards for the docs site and web app (#457)
* docs: add OpenGraph and Twitter card tags to the book and web app

Links to the docs site and the browser app now unfurl with a preview card.
The book gets the tags through theme/head.hbs (og:title follows the page
title); the web app's index.html carries its own, pointing at /app/. Both
use the social preview image, now served from the book at /solidcraft/og.png.

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

* docs: list the social preview image in ATTRIBUTION.md

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

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:45:21 -04:00
NOWN-DEVandClaude Opus 5.5 fb4c1d74b9 feat: edge flanges with their own angle and height per edge (#447)
One edge flange feature gave all its flanges one height and one angle. A
part whose flanges lean differently took one feature per angle, and
flanges of separate features don't close toward each other, so its
corners stayed open.

- sheet.flange (edge): an entry of `edges` is a point [x, y, z] as before,
  or {point, angle?, height?} whose angle and height (expressions, checked
  like the feature's) replace the feature's for that edge. Bad input is
  refused with the edge named. Before, the object form was read for its
  point and the rest was ignored without a word.
- The feature stores its edges as FlangeEdge. An edge without values of
  its own is written as the plain point it always was, so designs without
  them are read and written as before; an unknown key in an edge is
  refused, not dropped.
- The edges' values are feature inputs of their own (Edge angle, Edge
  height, after the feature's), so parameters, renames and
  parameters.change reach them. timeline.edit names the edges again when
  it changes them, so each value stays with its edge.
- SheetBody::add_flanges_each trims every edge by its own setback and
  adds the flanges. The corners between them close as between equal
  flanges: the closing already works from the two flanges it joins (each
  one's fold and leg), so the flat pattern follows by hand from the
  angles, the heights and the rule (the tests derive it).
- The Flange dialog shows a row per picked edge with an angle and a
  height box (empty: the dialog's value) once more than one edge is
  picked, and brings them back on Edit Feature.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:33:49 -04:00
Irakli KourtisandClaude Opus 5.5 0dee9deec1 Sketch constraints accept x / y for the sketch's own axes (#394) (#448)
`sketch.constraint.symmetry` (line), `parallel`, `perpendicular`,
`collinear`, `tangent` and `coincident` (point on curve) now take `x` /
`y` for the sketch's own axes, matching what `solid.revolve` means by
lowercase x|y. A real curve with that id still wins.

An axis becomes a linked construction line on it: a new
`LinkSource::SketchAxis` resolved in sketch coordinates, so it stays on
the sketch's axis on any plane, is shared by every constraint on that
axis, is left out of profiles and is reported in `created`. Equal,
concentric, midpoint and horizontal/vertical keep refusing axes, since
the axis line's finite length or fixed direction would make them
meaningless.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:31:35 -04:00
Irakli KourtisandClaude Opus 5.5 722bd007c8 Join keeps exact face attachments on faces it leaves unchanged (#420) (#453)
A Union dropped every construction certificate, so after any join no face
could take a durable sketch: sketch.create failed with "this face has no
exact planar source" even on a face the join never touched (#420, and the
"CREATE SKETCH: operation failed" reports #344, #195).

- kernel: map planar sources through a Union from both operands, with the
  same exact-support rule Cut uses. Coplanar supports of the two operands
  carry distinct identities, so a face merged from both stays without one.
- doc: Join extrude and Join box tools are certified like new bodies; a
  cap moved off its designed height (into the target, or past a curved To
  face) is not certified. Join producers are accepted when certifying.

Tests: both operands' faces attach and follow an upstream edit and a
reopen; a merged coplanar face is refused and fixed:true works; a face a
join covers whole is lost without moving to the new roof. Tests that used
a joined face as their "uncertified" example now use a merged face; the
trimmed-face case in a_sketch_on_a_face_stays_on_that_face now stays
attached to the surviving piece.


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

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:31:32 -04:00
NOWN-DEVandClaude Opus 5.5 9e60080b10 fix: fit the plane of a thin sewn face about its own centre (#441)
kernel::sew refused a solid with a thin planar face away from the origin: a slab
30 x 2 mm and 0.00013 mm thick with a corner at (-300, -200, 5), turned 0.45 rad
about x, failed with "sew: face 1: cannot attach a plane to a wire that is not on
one plane", though its faces are flat to 1e-14 mm.

truck's plane fit sums the cross products of a boundary's control points about a
centre and takes the points' sum for that centre (it divides by the number of wires,
not of points): eight times as far from the origin as a rectangle of four lines. The
sum is the same about any centre, but its rounding grows with the square of the
centre's distance, and on a thin face, whose normal is short, it turns the normal far
enough for truck's own 1e-6 flatness test to fail: truck finds the slab's four thin
faces 3.0e-7 to 2.1e-6 mm out of the planes it fitted.

A planar face keeps truck's plane wherever truck gives one. Where truck refuses, the
same fit is made on a copy of the boundary moved so that the mean of its control
points is the origin, and the plane is moved back; a boundary that leaves its plane
or has no area is refused as before.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:27:13 -04:00
NOWN-DEVandClaude Opus 5.5 455ca86a6c fix: write each lump of a body as its own STEP solid (#455)
A body of several pieces (a plate and the islands a cut leaves inside its
holes, such as letter counters) was written as one BREP_WITH_VOIDS whose
other shells were voids. Export now writes one solid per lump, each with
its face colours; a real void stays a void of its lump.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:27:09 -04:00
bherbruck 6fde5ff55b Update funding information in FUNDING.yml 2026-10-12 20:26:45 -04:00
MqriusandClaude Opus 5.5 7f92093cc8 fix(kernel): revolve with edges on the axis is built around the origin, then moved (#452)
A full revolve of a profile with more than one edge on the axis (a spherical shell's half ring:
both radial lines lie on it) took the generic sweep, built in place. Truck tessellates the
degenerate faces those edges sweep reliably only with the axis through the world origin, so on a
sketch plane off it the body measured about a third too large (a shell from r 18.5 to 40, 10 mm
above XY: 324 339 mm³ instead of 241 561), and booleans with it failed or removed the wrong side.

Such profiles are now swept about an axis through the origin and translated into place, as
revolve_touching_axis already does for profiles with one edge on the axis.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 20:25:46 -04:00
selune ddd528abaf fix(ui): accept toolbar drops across panel labels and gaps (#454) 2026-10-12 20:25:38 -04:00
selune 5c7149e57d fix(ui): put Point on the default sketch toolbar (#445) 2026-10-12 20:13:31 -04:00
614 changed files with 111589 additions and 7616 deletions

No files matched your search

+1
View File
@@ -10,6 +10,7 @@ not (
(package(solidcraft-engine) & (
test(=tests::hostile_params_never_panic)
| test(/^tests::robustness::/)
| test(/^tests::robustness_goldens::/)
| test(=pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen)
| test(=pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs)
| test(=cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall)
+4
View File
@@ -0,0 +1,4 @@
# These are supported funding model platforms
github: [bherbruck]
ko_fi: bherbruck
+99 -51
View File
@@ -5,58 +5,104 @@ parameter to watch the timeline rebuild. [Documentation](https://bherbruck.githu
[issues](https://github.com/bherbruck/solidcraft/issues). AI agents can model with it over MCP:
`solidcraft-cli mcp` ([how](https://bherbruck.github.io/solidcraft/guide/mcp.html)).
**Why 0.6:** this is a minor bump because two things change the files you work with: the
rename to SolidCraft and the new design file format. Files from earlier builds open fine, but
builds 0.5 and older can't open designs saved by 0.6.
**Why 0.9:** this is a minor bump because the design file format moved to version 17, and
because it adds many new features.
**What's new**
- **New design file format.** Saves are a zip container by default; plain JSON is still one
click away (details below).
- **Sheet metal:** flange corners close at any angle, and cut-outs fold across bends at any
angle, including curved bends.
- **Crash reports:** after a crash, Help ▸ Crash Reports… shows what happened with a ready-to-file
issue draft. Reports stay on your computer; nothing is sent. `solidcraft-cli crashes` lists and
replays them.
- **Snap package** for x86_64 and aarch64 on every release, alongside the AppImage, `.deb`, `.rpm`
and Flatpak.
- **Trackpad navigation** (two-finger pan, pinch zoom), a fillet option that shrinks a radius too
big for its edges, cleaner dialogs and toolbar panels, and the unfinished Generative Design
workspace is gone.
- Many modelling fixes: extrude/revolve in moved components, To Object picks, very flat arcs,
touching lines and arcs in booleans, and `solidcraft-cli run` now fails when a feature fails.
- **Face actions work on about 90% of flat faces.** Sketches, offset planes and press-pull now
attach to sheet metal rims and holes, plastic features (Boss, Lip, Rest, Snap Fit), lofts,
revolved faces, pieces Offset Face moved and walls a Join merged
([#677](https://github.com/bherbruck/solidcraft/pull/677), fixing
[#681](https://github.com/bherbruck/solidcraft/issues/681)). Across the test models: sketch
75% → 90%, offset plane 75% → 90%, press-pull 64% → 85%.
- **Joins:** a logo-style part with pieces extruded up and down from one sketch now joins
([#679](https://github.com/bherbruck/solidcraft/issues/679)), faces that only partly overlap
fuse correctly ([#704](https://github.com/bherbruck/solidcraft/pull/704), fixing
[#674](https://github.com/bherbruck/solidcraft/issues/674)), and booleans build the same body
every time ([#748](https://github.com/bherbruck/solidcraft/pull/748)), so a design rebuilds
identically on every run.
- **Sketch:** type a dimension right after picking (D, click, type;
[#695](https://github.com/bherbruck/solidcraft/pull/695)); redundant but consistent
constraints are accepted, such as a rectangle clover
([#699](https://github.com/bherbruck/solidcraft/pull/699)); tool windows for Scale, Offset,
Mirror, Move/Copy, Fillet and Chamfer and the rectangular pattern (thanks @wdunn001), shown
next to the Sketch Palette, which now lists the running tool's options; a typed Rho for the
conic; a Construction Geometries row in the Sketch Palette that hides construction geometry
([#804](https://github.com/bherbruck/solidcraft/pull/804)); sketch tool shortcuts open a sketch on the selected plane; Edit Sketch from the viewport
menu.
- **Features:** feature history on the left side with folders (opt in under Preferences;
[#449](https://github.com/bherbruck/solidcraft/pull/449)); Convert to Sheet Metal from a solid
or shelled body by its bend edges ([#725](https://github.com/bherbruck/solidcraft/pull/725));
a tear corner relief ([#809](https://github.com/bherbruck/solidcraft/pull/809)); tapered and
variable fillet radii ([#675](https://github.com/bherbruck/solidcraft/pull/675),
[#749](https://github.com/bherbruck/solidcraft/pull/749)) with a radius spline you drag on the
edge ([#752](https://github.com/bherbruck/solidcraft/pull/752)); free-form cut-outs stay exact
in sheet-metal flat patterns ([#692](https://github.com/bherbruck/solidcraft/pull/692)) and edge
flanges run along what cut-outs leave of an edge
([#702](https://github.com/bherbruck/solidcraft/pull/702)); and Surface Extrude, sheets from
open curves and profile walls ([#815](https://github.com/bherbruck/solidcraft/pull/815)).
- **View:** Home and Fit frame only what is shown, and a middle double-click goes Home
([#701](https://github.com/bherbruck/solidcraft/pull/701)); a cube navigator option
(thanks @Karl-VonRichter, [#664](https://github.com/bherbruck/solidcraft/pull/664)); the window
stays responsive and draggable while a design opens, with a Cancel button that stays put
([#643](https://github.com/bherbruck/solidcraft/pull/643),
[#686](https://github.com/bherbruck/solidcraft/pull/686)).
- **Quality:** cuts no longer split walls they don't touch
([#670](https://github.com/bherbruck/solidcraft/pull/670)); booleans are faster and use less
memory ([#714](https://github.com/bherbruck/solidcraft/pull/714)); STL export welds shared
vertices ([#816](https://github.com/bherbruck/solidcraft/pull/816)); complete Japanese
translation ([#753](https://github.com/bherbruck/solidcraft/pull/753)).
- **Fixes:** pasted features act on the pasted bodies (#613); a refused OK's error clears when
you change the inputs (#641); dialogs show the units of their sizes (#630); the wheel over a
dialog's list scrolls only the list (#678); a Single hole picked at a circle's centre lands on
the centre (#657); axis or tangent plane on a drill tip works (#567); sketch text edits from
letter interiors and moves as a whole; a projected edge stays the edge you picked; a size from
a bare diameter keeps its unit (#716); Convert Mesh says how big a mesh is and what converts;
the Save changes prompt takes arrow keys and Enter.
- **MCP:** agent skills for modeling and editing over MCP
([#728](https://github.com/bherbruck/solidcraft/pull/728)).
- A new README demo and the SELENE rover showcase.
**New file format.** Save and Save As now write `.solidcraft` designs as a zip container
that stores imported STEP, mesh and image data as binary, once each, at full precision, so
designs with imports save and open faster. What that means for you:
- Your existing `.solidcraft` and `.solvecraft` files (plain JSON) still open as before; the
format is detected from the contents.
- Want plain JSON (for diffs or scripts)? Use **File ▸ Save As JSON…**, or pick a
`.json` name in Save As; later saves keep JSON. **File ▸ Export Design as JSON…** writes a
separate JSON copy. From the CLI, `solidcraft-cli run … --save design.json`; from MCP or
scripts, pass `"format": "json"` to `save` / `file.save`.
- SolidCraft 0.5 and older can't open designs saved by 0.6, as a container or as JSON (the
JSON format tag is now `solidcraft/14`). Keep a copy of a file you may still open in 0.5.
**File format 17.** Designs are now format 17 (`solidcraft/17`). SolidCraft 0.8 and older can't
open designs saved by 0.9, as a container or as JSON; they ask you to update instead. Keep a
copy of a file you may still open in 0.8. Files from every earlier version still open in 0.9.
**Renamed to SolidCraft.** Everything now uses the new name: the `solidcraft` and
`solidcraft-cli` programs, `.solidcraft` designs, `SOLIDCRAFT_*` environment variables,
settings folders, package names and the `solidcraft` MCP server. Upgrading from SolveCraft:
- `.solvecraft` designs still open (double-click, Open, drag-drop, the CLI and MCP); new saves
are `.solidcraft`.
- Settings, preferences and recovery files are copied to the new folders on first launch; the
old folders are left in place. `SOLVECRAFT_*` environment variables are still read.
- `solvecraft-cli` remains as a deprecated alias for one release; it forwards to
`solidcraft-cli` and prints a note. Agents: `claude mcp add solidcraft -- solidcraft-cli mcp`;
an existing `claude mcp add solvecraft -- solvecraft-cli mcp` setup keeps working.
- Windows: the `.msi` upgrades the existing installation in place (now in
`Program Files\SolidCraft`). Linux: the `solidcraft` `.deb`/`.rpm` replaces the `solvecraft`
package. The Flatpak app ID stays `ai.storyteller.solvecraft`, so `flatpak update` just works,
and existing AppImages find the update.
- macOS: quit the app, move the old `SolveCraft.app` to the Trash (your designs and settings
stay), then drag `SolidCraft.app` from the DMG onto Applications and pin it in the Dock.
- Details: [compatibility notes](https://github.com/bherbruck/solidcraft/blob/main/docs/compatibility.md).
**Upgrading from 0.8.** Nothing to do: open your designs and save (they're saved as format 17
from then on). Feature-history folders on
the left are off by default; turn them on in Preferences.
Documentation, the browser app and the Flatpak feed use the SolidCraft project Pages URLs;
old-name Pages addresses are not permanent aliases.
**Known limitations**
- Some through-all holes along a wall or beside curved faces still fail
([#559](https://github.com/bherbruck/solidcraft/issues/559)), as does a hole crossing an
equal-radius hole ([#788](https://github.com/bherbruck/solidcraft/issues/788)).
- Some fillets are still refused, such as along an outline of lines and arcs
([#431](https://github.com/bherbruck/solidcraft/issues/431)).
- Some faces still can't take an attached sketch: a revolve's flat faces before a later feature
([#726](https://github.com/bherbruck/solidcraft/issues/726)) and faces after Split Body or
Combine Intersect ([#565](https://github.com/bherbruck/solidcraft/issues/565)).
- Fillet and chamfer accept an edge pick far from any edge and change the nearest one
([#783](https://github.com/bherbruck/solidcraft/issues/783)); Press Pull fails on faces beside
rounds ([#562](https://github.com/bherbruck/solidcraft/issues/562)).
- `sheet.fold` refuses a fixed point exactly on the sheet face
([#685](https://github.com/bherbruck/solidcraft/issues/685)), and some tabs on L-shaped or
collinear blanks can't be folded ([#638](https://github.com/bherbruck/solidcraft/issues/638),
[#639](https://github.com/bherbruck/solidcraft/issues/639)); an edge flange with different
per-edge angles leaves gaps at the corners
([#501](https://github.com/bherbruck/solidcraft/issues/501)).
- More in [ROADMAP.md](https://github.com/bherbruck/solidcraft/blob/main/ROADMAP.md#known-limitations).
**Upgrading from 0.7 or earlier.** 0.8 changed the Linux app ID to `ai.storyteller.solidcraft`:
`flatpak update` offers to replace the old app, and the `.deb`, `.rpm` and AppImage remove the old
menu entry.
**Upgrading from 0.5 or earlier.** 0.6 renamed SolveCraft to SolidCraft and made the zip
container the default save format. `.solvecraft` designs, old settings folders and
`SOLVECRAFT_*` variables are still picked up, and `solvecraft-cli` still forwards to
`solidcraft-cli`. Want plain JSON? Use **File ▸ Save As JSON…** or `--save design.json`. On
macOS, replace `SolveCraft.app` with `SolidCraft.app`. Details:
[compatibility notes](https://github.com/bherbruck/solidcraft/blob/main/docs/compatibility.md).
Documentation, the browser app and the Flatpak feed use the SolidCraft project Pages URLs.
**Which file?**
- **Windows:** `SolidCraft-{version}-windows-x64.exe` runs as is. Or install the `.msi`, or unzip the
@@ -78,10 +124,13 @@ installing SolidCraft; keep both remotes in the same user installation:
```sh
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --user --if-not-exists solidcraft https://bherbruck.github.io/solidcraft/flatpak/solidcraft.flatpakrepo
flatpak install --user solidcraft ai.storyteller.solvecraft
flatpak run ai.storyteller.solvecraft
flatpak install --user solidcraft ai.storyteller.solidcraft
flatpak run ai.storyteller.solidcraft
```
Flatpak installs from 0.7 or earlier use the old ID `ai.storyteller.solvecraft`: `flatpak update --user`
offers to replace it with `ai.storyteller.solidcraft`, and settings move along.
Single-file `.flatpak` bundles include the runtime repository address; missing runtimes
still need an internet connection. [Linux installation details](https://bherbruck.github.io/solidcraft/guide/getting-started.html#linux-flatpak).
@@ -89,6 +138,5 @@ The builds aren't signed yet. On macOS, right-click `SolidCraft.app` in Applicat
**Open** the first time (or run `xattr -dr com.apple.quarantine /Applications/SolidCraft.app`).
On Windows, SmartScreen may say "Windows protected your PC": **More info**, then **Run anyway**.
Licences are built in (Help ▸ About ▸ Licences, `solidcraft-cli licences`); the packages also
install `THIRD-PARTY-LICENSES.txt`. Known limitations:
[ROADMAP.md](https://github.com/bherbruck/solidcraft/blob/main/ROADMAP.md#known-limitations).
install `THIRD-PARTY-LICENSES.txt`.
+62 -4
View File
@@ -21,19 +21,34 @@ permissions:
contents: read
concurrency:
group: ci-${{ github.ref }}
# Every push to main gets its full run; a new PR push replaces the old run.
# A new PR push replaces the old run. Only one run per group may wait, so a busy main would
# cancel the waiting nightly (or a Run workflow) before it starts: they get groups of their own.
group: ci-${{ github.event_name }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
CARGO_TERM_COLOR: always
jobs:
dependency-advisories:
name: Dependency advisories
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: dtolnay/rust-toolchain@stable
- name: Install advisory scanner
run: cargo install cargo-deny --version 0.20.2 --locked
- name: Check locked dependencies
run: cargo deny --locked check --warn unmaintained advisories bans sources
changes:
name: Select checks
runs-on: ubuntu-24.04
outputs:
native: ${{ steps.paths.outputs.native }}
kernel: ${{ steps.paths.outputs.kernel }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
@@ -46,12 +61,14 @@ jobs:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
native=false
kernel=false
if [ "$EVENT_NAME" != pull_request ]; then
native=true
else
# Compare both old and new paths of renames; unknown paths run native checks.
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/ci-paths"
while IFS= read -r -d '' path; do
case "$path" in crates/kernel/*|vendor/truck-*) kernel=true ;; esac
case "$path" in
.github/workflows/ci.yml) native=true ;;
*.rs|Cargo.toml|Cargo.lock|*/Cargo.toml|.cargo/*|rust-toolchain*) native=true ;;
@@ -61,6 +78,7 @@ jobs:
done < "$RUNNER_TEMP/ci-paths"
fi
echo "native=$native" >> "$GITHUB_OUTPUT"
echo "kernel=$kernel" >> "$GITHUB_OUTPUT"
checks:
name: Format and repository checks
@@ -82,6 +100,8 @@ jobs:
run: cargo xtask licences --check
- name: Dependency layers
run: cargo xtask layers
- name: Web app offline use and updates (headless Chrome)
run: cargo xtask web-offline-test
clippy:
name: Native Linux Clippy
@@ -137,7 +157,7 @@ jobs:
tests:
name: Native Linux tests (${{ matrix.shard }}/4)
needs: build-tests
needs: [changes, build-tests]
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
@@ -164,6 +184,16 @@ jobs:
# the build job's path, which is the same checkout path here.
cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \
--profile ci --partition "slice:$SHARD/4" "${all[@]}"
- name: Robustness goldens sample (pull requests that touch the kernel)
# Every 8th seed of tests/robustness/goldens.json, a quarter per shard; pushes to main and
# the nightly run every seed in the step above.
if: github.event_name == 'pull_request' && needs.changes.outputs.kernel == 'true'
env:
SHARD: ${{ matrix.shard }}
run: |
cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \
--profile ci --ignore-default-filter \
-E "package(solidcraft-engine) & test(=tests::robustness_goldens::sample_$((SHARD - 1)))"
- name: Upload test timings
if: always()
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
@@ -172,6 +202,34 @@ jobs:
path: target/nextest/ci/junit.xml
retention-days: 7
action-matrix:
# Every ordinary action on every face, edge, vertex and body of the design corpus, against
# tests/action_matrix/baseline.json (docs/action-matrix.md). Every test run checks a sample;
# the whole matrix runs nightly (or by hand), a quarter per job.
name: Action matrix (${{ matrix.part }}/4)
needs: build-tests
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-24.04
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
part: [1, 2, 3, 4]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: taiki-e/install-action@1168f6810a3cb30a3514bf5756a3b7f0422c6df8 # v2.87.28
with:
tool: cargo-nextest
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: nextest-archive
- name: Run a quarter of the matrix
env:
PART: ${{ matrix.part }}
run: |
cargo nextest run --archive-file tests.tar.zst --workspace-remap . --extract-to . --extract-overwrite \
--profile ci --run-ignored only -E "binary(action_matrix) & test(=action_matrix_full_$PART)"
doc-tests:
name: Doc tests
needs: changes
@@ -315,4 +373,4 @@ jobs:
run: |
. "$HOME/.cargo/env"
export CRAFT_FONTS_DIR="$PWD/craft-fonts"
cargo check --all-targets --locked --target riscv64gc-unknown-linux-gnu
cargo check --all-targets --locked --target riscv64gc-unknown-linux-gnu
+11 -7
View File
@@ -6,9 +6,9 @@ name: Packaging lint
on:
push:
branches: [main]
paths: [".github/workflows/**", "packaging/**"]
paths: [".github/workflows/**", "packaging/**", "assets/**"]
pull_request:
paths: [".github/workflows/**", "packaging/**"]
paths: [".github/workflows/**", "packaging/**", "assets/**"]
workflow_dispatch:
permissions:
@@ -52,10 +52,14 @@ jobs:
}
- name: XML, desktop entry, AppStream, Flatpak manifest
run: |
xmllint --noout packaging/windows/solidcraft.wxs packaging/windows/solidcraft.en-us.wxl packaging/macos/*.plist* packaging/linux/*.xml*
desktop-file-validate packaging/linux/ai.storyteller.solvecraft.desktop
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' packaging/linux/ai.storyteller.solvecraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.solvecraft.metainfo.xml"
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.solvecraft.metainfo.xml"
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.solvecraft.bundle.yml')); assert m['id'] == 'ai.storyteller.solvecraft' and m['command'] == 'solidcraft'"
xmllint --noout assets/platforms/windows/solidcraft.wxs assets/platforms/windows/solidcraft.en-us.wxl assets/platforms/macos/*.plist* assets/platforms/linux/*.xml*
desktop-file-validate assets/platforms/linux/ai.storyteller.solidcraft.desktop
# The window's X11 class is its app id; the old id stays only as the rename shims.
grep -qx 'StartupWMClass=ai.storyteller.solidcraft' assets/platforms/linux/ai.storyteller.solidcraft.desktop
grep -q '<id>ai.storyteller.solvecraft</id>' assets/platforms/linux/ai.storyteller.solidcraft.metainfo.xml.in
grep -qF -e '--end-of-life-rebase=' packaging/flatpak-site.sh
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' assets/platforms/linux/ai.storyteller.solidcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.solidcraft.metainfo.xml"
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.solidcraft.metainfo.xml"
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.solidcraft.bundle.yml')); assert m['id'] == 'ai.storyteller.solidcraft' and m['command'] == 'solidcraft'"
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/snap/snapcraft.yaml')); assert m['name'] == 'solidcraft' and m['confinement'] == 'strict' and m['apps']['solidcraft']['command'] == 'usr/bin/solidcraft'"
python3 -m py_compile packaging/linux/snap-smoke.py
+2
View File
@@ -50,6 +50,8 @@ jobs:
- name: Build the web app
working-directory: apps/solidcraft-web
run: trunk build --release --public-url /solidcraft/app/
- name: Test the web app offline and across an update (headless Chrome)
run: cargo xtask web-offline-test --site dist/web
- name: Assemble the site
run: |
mkdir -p site
+10 -4
View File
@@ -59,8 +59,8 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
nothing from FreeCAD is linked, vendored or shipped.) The sketch solver and the blend
operations are our own.
- **Assets — absolutely essential.** SolidCraft contains **no iconography, images, fonts or
artwork from commercial CAD products — ever.** Every icon is drawn in code
(`crates/ui-egui/src/icons.rs`). Any file asset must be original or openly licensed and **must have a row in ATTRIBUTION.md**
artwork from commercial CAD products — ever.** Every UI icon is drawn in collected code
(`assets/icons/ui/source/`, compiled through `crates/ui-egui/src/icons.rs`). Any file asset must be original or openly licensed and **must have a row in ATTRIBUTION.md**
(`cargo xtask assets` enforces it). Screenshots of commercial CAD software are never committed.
Breaking this rule is the most serious mistake you can make in this repo.
- **Everything is a command.** User-visible behaviour = a command in `crates/engine/src/cmd/*`
@@ -75,7 +75,10 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
egui/eframe/winit/wgpu/rfd. **The UI crate is swappable.** Only `crates/kernel` may use `truck`.
- **The UI is thin**: panels read engine state and act through `app.run(id, params)` /
`app.start(id)`. Colours come from `theme::Tokens`.
- **Rust only.** Units are millimetres and radians internally; Z is up.
- **Rust only.** Units are millimetres and radians internally; Z is up. The one exception is
JavaScript the browser requires: the service worker `apps/solidcraft-web/sw.js` (browsers only
run service workers as JavaScript) and the small inline scripts in `apps/solidcraft-web/index.html`.
Keep them minimal; `cargo xtask web-offline-test` covers the service worker.
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests,
assets, layers). Commit after every feature arc that builds.
- **Publishing**: `origin` is github.com/bherbruck/solidcraft. Changes land through **pull requests**:
@@ -122,7 +125,10 @@ before the draft is verified.
## Running and looking at the app
- `cargo run --release -p solidcraft -- --sample --control PORT` (sample design + control channel).
Pick a free port. Without a display, run it under `Xvfb :99` with `DISPLAY=:99`.
- Drive it with JSON lines on `127.0.0.1:PORT` (see `docs/control-protocol.md`):
- Drive it with JSON lines on `127.0.0.1:PORT` (see `docs/control-protocol.md`). The first
request on a connection carries the launch's token as `"token"`: read it from
`~/.local/share/solidcraft/control/PORT.token`, or start the app with
`SOLIDCRAFT_CONTROL_TOKEN=$(openssl rand -hex 32)` and send that.
- `{"id":1,"method":"engine.execute","params":{"command":"solid.extrude","params":{"distance":20}}}`
- `{"id":2,"method":"ui.start","params":{"command":"sketch.create"}}` then `ui.click {x, y}`
- `{"id":3,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}` then read the PNG.
+77 -28
View File
@@ -8,44 +8,44 @@ file is missing from this table.
templates, materials or help text.** Every asset is original work by SolidCraft contributors or
third-party material under an open licence. Screenshots of Autodesk software are never committed.
Generated-in-code assets are original and have no file to list:
- the UI icon set (`crates/ui-egui/src/icons.rs`);
- the axis view, navigation bar and timeline glyphs (`crates/ui-egui/src/viewport.rs`,
`crates/ui-egui/src/timeline.rs`).
Editable drawing code, native projects and shipped exports are collected under `assets/`.
## Files
| File | Author | Source | Licence |
|---|---|---|---|
| assets/icon.svg | SolidCraft contributors | original macOS adaptation of the code-drawn SolidCraft mark, edited and exported with VectorCraft; Apple HIG and production templates consulted as layout references only | MIT OR Apache-2.0 |
| assets/SolidCraft.icon/icon.json | SolidCraft contributors | approved Icon Composer composition and appearance settings for the original SolidCraft artwork | MIT OR Apache-2.0 |
| assets/SolidCraft.icon/Assets/02-grid.svg | SolidCraft contributors | original perspective grid with elliptical radial alpha, exported from VectorCraft | MIT OR Apache-2.0 |
| assets/SolidCraft.icon/Assets/02-grid-dark.svg | SolidCraft contributors | original blue perspective grid with elliptical radial alpha, exported from VectorCraft for the Dark appearance | MIT OR Apache-2.0 |
| assets/SolidCraft.icon/Assets/03-solid-blue-cutout.svg | SolidCraft contributors | original saturated blue CAD solid with transparent topology cutouts, edited and exported with VectorCraft | MIT OR Apache-2.0 |
| assets/Assets.car | SolidCraft contributors | Apple actool compilation of assets/SolidCraft.icon for macOS; contains original artwork only | MIT OR Apache-2.0 |
| assets/icon.vectorcraft | SolidCraft contributors | editable VectorCraft source for assets/icon.svg, with named face and background layers | MIT OR Apache-2.0 |
| assets/icon.png | SolidCraft contributors | 1024 × 1024 transparent sRGB export of assets/icon.svg from VectorCraft | MIT OR Apache-2.0 |
| assets/icon.icns | SolidCraft contributors | macOS fallback compiled by Apple's actool from the approved assets/SolidCraft.icon composition | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_16x16.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_16x16@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_32x32.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_32x32@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_128x128.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_128x128@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_256x256.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_256x256@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_512x512.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/icon.iconset/icon_512x512@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.svg | SolidCraft contributors | original macOS adaptation of the code-drawn SolidCraft mark, edited and exported with VectorCraft; Apple HIG and production templates consulted as layout references only | 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 |
| assets/icons/app/macos/source/SolidCraft.icon/Assets/02-grid-dark.svg | SolidCraft contributors | original blue perspective grid with elliptical radial alpha, exported from VectorCraft for the Dark appearance | MIT OR Apache-2.0 |
| assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg | SolidCraft contributors | original saturated blue CAD solid with transparent topology cutouts, edited and exported with VectorCraft | MIT OR Apache-2.0 |
| 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 | editable VectorCraft source for assets/archive/icons/macos-v1/exports/icon.svg, with named face and background layers | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.png | SolidCraft contributors | 1024 × 1024 transparent sRGB export of assets/archive/icons/macos-v1/exports/icon.svg from VectorCraft | 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/archive/icons/macos-v1/exports/icon.iconset/icon_16x16.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_16x16@2x.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_32x32.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_32x32@2x.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_128x128.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_128x128@2x.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_256x256.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_256x256@2x.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_512x512.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| assets/archive/icons/macos-v1/exports/icon.iconset/icon_512x512@2x.png | SolidCraft contributors | native sips export from assets/archive/icons/macos-v1/exports/icon.png | MIT OR Apache-2.0 |
| docs/screenshots/sample-plate.png | SolidCraft contributors | screenshot of SolidCraft itself (`solidcraft --sample`) | MIT OR Apache-2.0 |
| docs/screenshots/enclosure-demo-0.2.1.gif | SolidCraft contributors | original electronics enclosure screen recording of the actual SolidCraft 0.2.1 English desktop UI, generated with examples/demo/record_ui.sh and its original animated pointer overlay (4× playback) | MIT OR Apache-2.0 |
| docs/screenshots/enclosure-demo-0.2.1.mp4 | SolidCraft contributors | original electronics enclosure screen recording of the actual SolidCraft 0.2.1 English desktop UI, generated with examples/demo/record_ui.sh and its original animated pointer overlay (4× playback) | MIT OR Apache-2.0 |
| docs/screenshots/demo-0.8.gif | SolidCraft contributors | original screen recording of the actual SolidCraft 0.8 English desktop UI modelling an original Raspberry Pi 4 case (no logos or branding; the engraved text is set in the system's DejaVu Sans Bold), generated with examples/demo/record_ui.sh and its original animated pointer overlay (9× playback) | MIT OR Apache-2.0 |
| docs/screenshots/demo-0.8.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.8 English desktop UI modelling an original Raspberry Pi 4 case (no logos or branding; the engraved text is set in the system's DejaVu Sans Bold), generated with examples/demo/record_ui.sh and its original animated pointer overlay (9× playback) | MIT OR Apache-2.0 |
| docs/screenshots/rover-build.gif | 0xlillith (model); SolidCraft contributors (recording) | screen recording of the actual SolidCraft desktop UI replaying the timeline of the SELENE rover (`Rover-recorded.solidcraft` from https://github.com/0xlillith/solidcraft-rover, an original model by 0xlillith built through SolidCraft MCP); the model's lettering follows Ubuntu Light outlines (Canonical Ltd., Ubuntu Font Licence 1.0, as noted in that repository); the emblem is the SolidCraft project icon (2× playback) | MIT OR Apache-2.0 |
| docs/screenshots/rover-build.mp4 | 0xlillith (model); SolidCraft contributors (recording) | screen recording of the actual SolidCraft desktop UI replaying the timeline of the SELENE rover (`Rover-recorded.solidcraft` from https://github.com/0xlillith/solidcraft-rover, an original model by 0xlillith built through SolidCraft MCP); the model's lettering follows Ubuntu Light outlines (Canonical Ltd., Ubuntu Font Licence 1.0, as noted in that repository); the emblem is the SolidCraft project icon | MIT OR Apache-2.0 |
| assets/branding/web/source/og.png | SolidCraft contributors | original 1280 × 640 social preview card (the SolidCraft mark, name and tagline), served by the docs site for link previews | MIT OR Apache-2.0 |
| docs/book/src/generated/sample-plate.png | SolidCraft contributors | copy of docs/screenshots/sample-plate.png made by `cargo xtask book` (gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/bracket.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot examples/bracket.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/enclosure.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/enclosure.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/revolve.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/revolve.json`; gitignored) | MIT OR Apache-2.0 |
| docs/book/src/generated/sheet-metal.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/sheet-metal.json`; gitignored) | MIT OR Apache-2.0 |
| crates/sketch/fonts/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in crates/sketch/fonts/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
| apps/solidcraft-web/fonts/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in apps/solidcraft-web/fonts/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
| tests/oracle/17-loft/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/17-loft/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
| tests/oracle/23-bracket-multifeature/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/23-bracket-multifeature/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
| tests/oracle/28-blind-pocket-fillets/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/28-blind-pocket-fillets/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
@@ -67,6 +67,45 @@ Generated-in-code assets are original and have no file to list:
| 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/fonts/biz-udp-gothic/OFL.txt | The BIZ UDGothic Project Authors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
| assets/icons/app/linux/exports/hicolor/128x128/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/16x16/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/24x24/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/256x256/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/32x32/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/48x48/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/512x512/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/linux/exports/hicolor/64x64/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/shared/source/app_icon.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/icons/app/shared/source/windows_ico.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/icons/app/windows/exports/solidcraft-executable.ico | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/app/windows/exports/solidcraft.ico | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
| assets/icons/ui/source/glyphs.rs | SolidCraft / VectorCraft contributors | Original glyphs; caption geometry adapted from VectorCraft titlebar.rs (https://github.com/storytold/vectorcraft) | MIT OR Apache-2.0 |
| assets/icons/ui/source/icons.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/icons/ui/source/markers.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/icons/ui/source/navigation.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/icons/ui/source/pointer.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/de.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/en.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/ja.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/ko.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/locales/zh.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/materials/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/platforms/linux/ai.storyteller.solidcraft.desktop | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/linux/ai.storyteller.solidcraft.metainfo.xml.in | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/linux/ai.storyteller.solidcraft.mime.xml | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/macos/Info.plist.in | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/macos/README.txt | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/windows/solidcraft.en-us.wxl | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/platforms/windows/solidcraft.wxs | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
| assets/shaders/viewport.wgsl | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/ui/palettes.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| assets/styles/web/loading.css | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
| docs/book/src/og.png | SolidCraft contributors | copy of assets/branding/web/source/og.png staged by cargo xtask book (gitignored) | MIT OR Apache-2.0 |
## Code adapted from sibling projects
| SolidCraft file | Adapted from | Licence |
@@ -83,6 +122,16 @@ Generated-in-code assets are original and have no file to list:
| LICENSE-MIT, LICENSE-APACHE (licence text) | CADCraft | — |
| .github/workflows/release.yml, .github/workflows/packaging-lint.yml and packaging/ (release pipeline: Linux AppImage/deb/rpm/Flatpak/tar.gz, FreeBSD, macOS dmg, Windows MSI/portable zip, web zip; AppRun, nfpm, Flatpak, WiX and Info.plist templates) | PhotoCraft (https://github.com/storytold/photocraft) `.github/workflows/{release,packaging-lint}.yml`, `packaging/` | Apache-2.0 |
## Incremental HDR codec adapter
`crates/render/src/lighting/hdr.rs` adapts the scanline/component/old-RLE decoding
state machines from the Radiance decoder in `image` 0.25.10
([source](https://docs.rs/crate/image/0.25.10/source/src/codecs/hdr/decoder.rs)),
MIT OR Apache-2.0. Metadata parsing still uses that library. The adapter makes row
work cooperative and adds checked run lengths, zero-run rejection, width validation,
header/input/radiance bounds and cancellation. Original licence texts are retained
in THIRD-PARTY-LICENSES.txt; no images or artwork from that project are copied.
## Recovered contributions
| SolidCraft file | Original contribution | Licence |
@@ -93,7 +142,7 @@ Generated-in-code assets are original and have no file to list:
| Directory | Source | Licence | Changes |
|---|---|---|---|
| vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: parameter searches kept inside the surface domain (vendor/README.md) |
| vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: bounded parameter searches, collapsed-boundary projection, subdivision and provisional fixed-grid previews (vendor/README.md) |
| vendor/truck-stepio | truck-stepio 0.3.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | STEP writer: intersection-curve surfaces written at unique ids (vendor/README.md) |
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; face and edge history of booleans; trace macro (vendor/README.md) |
| vendor/matext4cgmath | matext4cgmath 0.1.0, https://github.com/ricosjp/matext4cgmath (RICOS Co. Ltd.) | Apache-2.0 | root refinement stops after 100 Newton steps (vendor/README.md) |
+8 -2
View File
@@ -75,7 +75,10 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
egui/eframe/winit/wgpu/rfd. **The UI crate is swappable.** Only `crates/kernel` may use `truck`.
- **The UI is thin**: panels read engine state and act through `app.run(id, params)` /
`app.start(id)`. Colours come from `theme::Tokens`.
- **Rust only.** Units are millimetres and radians internally; Z is up.
- **Rust only.** Units are millimetres and radians internally; Z is up. The one exception is
JavaScript the browser requires: the service worker `apps/solidcraft-web/sw.js` (browsers only
run service workers as JavaScript) and the small inline scripts in `apps/solidcraft-web/index.html`.
Keep them minimal; `cargo xtask web-offline-test` covers the service worker.
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests,
assets, layers). Commit after every feature arc that builds.
- **Publishing**: `origin` is github.com/bherbruck/solidcraft. Changes land through **pull requests**:
@@ -99,7 +102,10 @@ before the draft is verified.
## Running and looking at the app
- `cargo run --release -p solidcraft -- --sample --control PORT` (sample design + control channel).
Pick a free port. Without a display, run it under `Xvfb :99` with `DISPLAY=:99`.
- Drive it with JSON lines on `127.0.0.1:PORT` (see `docs/control-protocol.md`):
- Drive it with JSON lines on `127.0.0.1:PORT` (see `docs/control-protocol.md`). The first
request on a connection carries the launch's token as `"token"`: read it from
`~/.local/share/solidcraft/control/PORT.token`, or start the app with
`SOLIDCRAFT_CONTROL_TOKEN=$(openssl rand -hex 32)` and send that.
- `{"id":1,"method":"engine.execute","params":{"command":"solid.extrude","params":{"distance":20}}}`
- `{"id":2,"method":"ui.start","params":{"command":"sketch.create"}}` then `ui.click {x, y}`
- `{"id":3,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}` then read the PNG.
+9 -1
View File
@@ -100,6 +100,8 @@ The [README](README.md#architecture) has the layer table. The short version:
not.) Permissive
dependencies are fine. Note which one you're adding and why in the PR, and run
`cargo xtask licences` so THIRD-PARTY-LICENSES.txt lists it (CI checks that it is current).
Nightly/manual CI also scans advisories, bans and sources with cargo-deny; see
[the local command and coverage limits](docs/book/src/dev/testing.md#dependency-advisories).
3. **Every asset is original or openly licensed and has a row in
[ATTRIBUTION.md](ATTRIBUTION.md).** Icons are drawn in code (`crates/ui-egui/src/icons.rs`).
`cargo xtask assets` enforces this.
@@ -159,13 +161,19 @@ Pull requests skip a few heavy tests (one to four minutes each), which run on ev
`main` and nightly; check the `main` run after merging. They are listed in
`.config/nextest.toml` and the [testing page](docs/book/src/dev/testing.md):
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`,
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness` and
`tests::robustness_goldens` (pull requests that touch `crates/kernel` run its `sample_*` part),
`pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen`,
`pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs`,
`cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall`
- `solidcraft-kernel`: `tests::boolean_identity_on_tangent_primitives`
- `solidcraft-ui-egui` scenarios: `acceptance_sensor_enclosure`
**The action matrix** tries every ordinary action on every face, edge, vertex and body of a
corpus of designs against a checked-in ratchet ([docs/action-matrix.md](docs/action-matrix.md)).
A pull request that makes an action pass should record it with `cargo xtask action-matrix --bless`;
one that makes a passing action fail fails the sample every CI run checks.
**The reference tests** are 76 parts in [`tests/oracle/`](tests/oracle/), each a `recipe.json`
in our own format with an `expected.json` beside it. SolidCraft rebuilds the recipe and compares
body count, volume, area, centre of mass and bounding box (and, where there is a `reference.stl`,
Generated
+36 -26
View File
@@ -2765,15 +2765,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c"
dependencies = [
"memchr",
]
[[package]]
name = "quick-xml"
version = "0.41.0"
@@ -3290,14 +3281,15 @@ dependencies = [
[[package]]
name = "solidcraft"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"eframe",
"egui",
"embed-resource",
"getrandom 0.3.4",
"hidapi",
"log",
"quick-xml 0.38.4",
"quick-xml",
"rfd",
"serde_json",
"solidcraft-engine",
@@ -3307,7 +3299,7 @@ dependencies = [
[[package]]
name = "solidcraft-cli"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"serde_json",
"solidcraft-engine",
@@ -3316,7 +3308,7 @@ dependencies = [
[[package]]
name = "solidcraft-doc"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"log",
"serde",
@@ -3331,7 +3323,7 @@ dependencies = [
[[package]]
name = "solidcraft-engine"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"log",
"serde",
@@ -3347,7 +3339,7 @@ dependencies = [
[[package]]
name = "solidcraft-geom"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"serde",
"serde_json",
@@ -3355,10 +3347,22 @@ dependencies = [
]
[[package]]
name = "solidcraft-io"
version = "0.6.0"
name = "solidcraft-gpu"
version = "0.10.1"
dependencies = [
"quick-xml 0.38.4",
"log",
"pollster 0.4.0",
"solidcraft-geom",
"solidcraft-kernel",
"thiserror 2.0.21",
"wgpu",
]
[[package]]
name = "solidcraft-io"
version = "0.10.1"
dependencies = [
"quick-xml",
"serde",
"serde_json",
"sha2",
@@ -3371,11 +3375,14 @@ dependencies = [
[[package]]
name = "solidcraft-kernel"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"log",
"rayon",
"serde",
"serde_json",
"solidcraft-geom",
"spade",
"thiserror 2.0.21",
"truck-meshalgo",
"truck-modeling",
@@ -3386,7 +3393,7 @@ dependencies = [
[[package]]
name = "solidcraft-mcp"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"serde_json",
"solidcraft-engine",
@@ -3394,7 +3401,7 @@ dependencies = [
[[package]]
name = "solidcraft-render"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"image",
"serde",
@@ -3404,7 +3411,7 @@ dependencies = [
[[package]]
name = "solidcraft-sketch"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"serde",
"serde_json",
@@ -3415,22 +3422,24 @@ dependencies = [
[[package]]
name = "solidcraft-ui-egui"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"egui",
"egui-wgpu",
"image",
"log",
"pollster 0.4.0",
"serde",
"serde_json",
"solidcraft-engine",
"solidcraft-gpu",
"ttf-parser",
"web-time",
]
[[package]]
name = "solidcraft-web"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"eframe",
"egui",
@@ -4181,7 +4190,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0"
dependencies = [
"proc-macro2",
"quick-xml 0.41.0",
"quick-xml",
"quote",
]
@@ -4853,9 +4862,10 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
[[package]]
name = "xtask"
version = "0.6.0"
version = "0.10.1"
dependencies = [
"serde_json",
"sha2",
]
[[package]]
+11 -3
View File
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
exclude = ["vendor"]
[workspace.package]
version = "0.6.0"
version = "0.10.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.95"
@@ -18,6 +18,7 @@ solidcraft-geom = { path = "crates/geom" }
solidcraft-sketch = { path = "crates/sketch" }
solidcraft-kernel = { path = "crates/kernel" }
solidcraft-render = { path = "crates/render" }
solidcraft-gpu = { path = "crates/gpu" }
solidcraft-doc = { path = "crates/doc" }
solidcraft-io = { path = "crates/io" }
solidcraft-engine = { path = "crates/engine" }
@@ -37,16 +38,20 @@ truck-shapeops = { version = "0.4", default-features = false }
truck-meshalgo = { version = "0.4", default-features = false, features = ["tessellation", "analyzers"] }
truck-stepio = { version = "0.3", default-features = false }
truck-topology = "0.6"
# constrained Delaunay + Ruppert refinement for trimmed faces (MIT OR Apache-2.0); already in the
# tree through truck-meshalgo.
spade = { version = "2.15", default-features = false, features = ["std"] }
rayon = "1.12"
# packages (3MF)
zip = { version = "3", default-features = false, features = ["deflate"] }
quick-xml = "0.38"
quick-xml = "0.41"
# fonts (sketch text outlines; MIT OR Apache-2.0, pure Rust)
ttf-parser = { version = "0.25", default-features = false, features = ["std"] }
# images
image = { version = "0.25", default-features = false, features = ["png"] }
image = { version = "0.25", default-features = false, features = ["png", "hdr"] }
# ui
egui = "0.36"
@@ -54,6 +59,9 @@ eframe = { version = "0.36", default-features = false, features = ["persistence"
# Instant/SystemTime that also work in the browser (std panics on wasm32-unknown-unknown).
web-time = "1"
egui-wgpu = "0.36"
# the wgpu egui-wgpu uses, for compute (crates/gpu); backends come from the apps' features
wgpu = { version = "30", default-features = false, features = ["std", "wgsl"] }
pollster = "0.4"
rfd = "0.17"
[workspace.lints.rust]
+5
View File
@@ -19,6 +19,11 @@ licensed MIT OR Apache-2.0; ATTRIBUTION.md lists the files.
The release packaging (.github/workflows/release.yml, packaging/) is adapted from PhotoCraft
(https://github.com/storytold/photocraft), licensed under the Apache License 2.0.
The incremental Radiance RGBE row adapter in crates/render/src/lighting/hdr.rs adapts
decoding state machines from image 0.25.10 (https://github.com/image-rs/image),
MIT OR Apache-2.0. ATTRIBUTION.md describes the bounded/cancellable changes; the
original licence texts are included in THIRD-PARTY-LICENSES.txt.
Most bundled assets are drawn in code or contributed originally; third-party fonts (Liberation Sans
for sketch text, BIZ UDPGothic for web Japanese display) are licensed under SIL OFL 1.1. The
per-file list is in ATTRIBUTION.md.
+29 -11
View File
@@ -6,9 +6,9 @@ Sketch, constrain, and build solids with a parametric feature timeline. Change a
and watch your model rebuild. Run it on your desktop, in your browser, or let AI agents
model through the built-in MCP server.
![SolidCraft: draw a rounded electronics enclosure, sketch its USB port directly on a face, add four mounting bosses and resize it through Parameters](docs/screenshots/enclosure-demo-0.2.1.gif)
![SolidCraft 0.8: model a Raspberry Pi 4 case from an empty design: a sketch dimensioned with parameters, extrude, fillets and shell, a rim rounded in one pick, standoffs patterned on the board's mounting holes, port cut-outs, vent slots, "Pi 4" engraved in a font, a PETG material with its mass in Properties, and a height change in Parameters](docs/screenshots/demo-0.8.gif)
*Start with an empty design and draw the original parametric electronics enclosure in the English desktop UI: rounded corners, a shell, a USB port sketched directly on the wall and a four-boss pattern. Change width from 80 → 100 mm and wall thickness from 2 → 3 mm in Parameters and watch it rebuild. Original animated pointer and click cues; 4× playback. [Full video](https://github.com/bherbruck/solidcraft/raw/refs/heads/main/docs/screenshots/enclosure-demo-0.2.1.mp4), generated with the [demo script](examples/demo/record_ui.sh).*
*A Raspberry Pi 4 case from an empty design: a sketch dimensioned with the board's parameters, extrude, fillet, shell, the rim rounded in one pick, an M2.5 standoff patterned on the 58 × 49 mm hole rectangle, port cut-outs, vent slots, "Pi 4" engraved in DejaVu Sans Bold and a PETG material defined in the design, with its mass in Properties, then height 24 → 32 mm in Parameters and it all rebuilds. 9× playback. [Full video](https://github.com/bherbruck/solidcraft/raw/refs/heads/main/docs/screenshots/demo-0.8.mp4), recorded with the [demo script](examples/demo/record_ui.sh).*
## Download
@@ -68,6 +68,16 @@ are listed by area in [docs/features.md](docs/features.md); status and plans are
Open work and known bugs are in [GitHub issues](https://github.com/bherbruck/solidcraft/issues),
and help is welcome: see [CONTRIBUTING.md](CONTRIBUTING.md).
## Built with SolidCraft
<a href="https://github.com/0xlillith/solidcraft-rover"><img src="docs/screenshots/rover-build.gif" alt="SELENE, a scientific rover by 0xlillith, assembling itself as the timeline plays through its 3,323 steps in SolidCraft: tub, wheels, electronics, suspension, sampling arm, solar wings, dish and camera turret" loading="lazy"></a>
*SELENE, a scientific rover built entirely through SolidCraft's MCP server by
[@0xlillith](https://github.com/0xlillith/solidcraft-rover): 3,323 editable timeline steps that
rebuild with no errors, replayed here at 2× speed.
[Full video](https://github.com/bherbruck/solidcraft/raw/refs/heads/main/docs/screenshots/rover-build.mp4)
· [download the editable model](https://github.com/0xlillith/solidcraft-rover/releases/tag/v1.0.0).*
## What works
- **Sketches** on origin planes, offset planes or planar faces: lines, rectangles, circles,
@@ -81,13 +91,17 @@ and help is welcome: see [CONTRIBUTING.md](CONTRIBUTING.md).
the boundary and keeps existing extrusions usable.
Curve drawing and picking adapt together to zoom and display scale, including small circles
at Fit and sketches in placed components.
- **Constraints and dimensions** solved by our own solver (damped Gauss–Newton with
degree-of-freedom analysis): coincident, horizontal/vertical, parallel, perpendicular, tangent,
equal, concentric, collinear, midpoint, symmetry, fix; linear, aligned, horizontal, vertical,
radius, diameter and angle dimensions driven by parameters. Over-constraining edits are
rejected; fully constrained geometry turns black.
- **Constraints and dimensions** solved by our own solver. Points the constraints fix relative
to known geometry are placed exactly by construction (loci intersections and rigid-cluster
decomposition, after Owen 1991 and Fudos–Hoffmann 1997); Levenberg–Marquardt solves the rest,
and the rank of its Jacobian gives the remaining degrees of freedom. Constraints: coincident,
point on curve, horizontal/vertical, parallel, perpendicular, tangent, curvature (G2), equal,
concentric, collinear, midpoint, symmetry, fix; linear, aligned, horizontal, vertical,
point-to-line, radius, diameter, angle, arc sweep, arc length and slot length dimensions driven
by parameters. Over-constraining edits are rejected; fully constrained geometry turns black.
Dragging solves the sketch every frame and rebuilds the model once, when the drag ends.
Select a line or its two endpoints, type its dimension and press Enter to commit one undo
step. Fit keeps sketch geometry clear of the palette and navigation controls.
step; with Sketch Dimension it's D, click, type, Enter. Fit keeps sketch geometry clear of the palette and navigation controls.
- **Profiles** found automatically, including regions split by lines and holes inside outlines.
Newly captured complete adaptive boundaries retain their outer and hole source identities
when spline span counts change. Partial or ambiguous boundaries do not qualify.
@@ -108,9 +122,11 @@ and help is welcome: see [CONTRIBUTING.md](CONTRIBUTING.md).
separately from through-hole outlines in the flat preview and DXF.
- **Files**: design containers (`.solidcraft`) with lossless binary mesh payloads;
plain JSON remains supported for opening, Save As and export. Desktop Open can show a saved
experimental preview (explicit `preview: true` engine saves) while checking geometry before editing. Export to STEP (AP242 with body names, colours and
provisional picture from warm geometry caches while rebuilding before editing; caches never
replace the parametric design. Export to STEP (AP242 with body names, colours and
components as assemblies; revolved faces as native planes, cylinders, cones, spheres and tori),
IGES 5.3 (manifold solid B-reps with names and colours), 3MF, STL (binary/ASCII) and OBJ;
IGES 5.3 (manifold solid B-reps with names and colours), 3MF, STL (binary/ASCII) and OBJ, and
assemblies with joints as URDF for robot simulators (docs/urdf.md);
3MF, STL and OBJ import as mesh bodies (they render, measure, move and export; solid features
need B-rep bodies). Convert Mesh reconstructs supported closed constant-section parts
as parametric solids with planar and circular cylindrical faces;
@@ -184,7 +200,9 @@ stdio. Headless it runs its own design session; with `--connect 127.0.0.1:PORT`
started with `solidcraft --control PORT`, so you can watch the agent model. Tools:
`list_commands`, `execute`, `batch`, `inspect_design`, `measure`, `body_topology`,
`set_parameter`, `screenshot` (returns the image), `export`, `undo`, `redo`, `new_design`, `open`,
`save`. Claude Code: `claude mcp add solidcraft -- solidcraft-cli mcp`. Details, client
`save`. Tool calls reach files only in the open design's folder (when it lies inside your home
folder, never home itself or a hidden folder), SolidCraft's recovery folder and folders given with
`--allow-path DIR`. Claude Code: `claude mcp add solidcraft -- solidcraft-cli mcp`. Details, client
configuration and an example session: [docs/mcp.md](docs/mcp.md).
## Architecture
+105 -16
View File
@@ -36,7 +36,7 @@ In scope, in priority order:
metal rules (thickness, K-factor, bend radius) (M10).
5. A small PLASTIC subset for enclosures, after sheet metal: Boss, Rib, Web, Lip/Groove, Snap Fit.
Deferred until further notice: MESH, FORM (T-splines), SURFACE beyond what solids need, PCB / 3D
Deferred until further notice: MESH, FORM (T-splines), SURFACE beyond what solids need (scope proposed in #772), PCB / 3D
PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
## Milestones
@@ -47,29 +47,80 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
| 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, 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 |
| 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 | in progress | 30 |
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), 3MF export and 3MF/STL import as mesh bodies (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)), 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, reorder with dependency checks, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names), 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; taper handle open), 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; optional compact named left feature history with rollback bar, multi-selection and persistent folders above the parts browser), 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; taper handle open), 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 |
| M7 | 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 | 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, 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), Exploded views (automatic or by hand, shown without changing placements); interference, per-component physical properties; STEP with assembly structure. 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 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 in Fusion's layout 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 | in progress | 36 |
| M8 | Surface workspace (#772). Done: Patch (sketch regions; edge loops filled G0), Stitch, Thicken (planes and cylinders), Extend (straight edges of planar surfaces), Trim (by a plane or a face), Delete Face (healing), Boundary Fill, zebra/draft/curvature-map analysis. Proposed, in order: surface Extrude/Revolve/Sweep/Loft from open and closed curves (#757), Offset surface (#758), Unstitch and Reverse Normal (#766), Delete Face without healing (#768); Extend curved faces and edges (#760), mutual trim and trim by projected curves (#767), Untrim (#761), Thicken every surface kind (#765); Ruled (#762), Boundary surface and curve networks with G0/G1/G2 (#763), Patch with G1/G2 and general loops (#759), Blend surface (#764), sweep rails, twist and scale (#771, loft rails #201), fillet between surface bodies (#769); continuity analysis, surface curvature combs and isocurves (#770) | 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, 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), 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 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 in Fusion's layout 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 |
## Physical material data
- Physical material card validation (#923/#922) provides typed SI mechanical and
thermal data, per-property sources/conditions and isotropic/orthotropic stability
checks through read-only `material.properties.validate`. It does not assign or
persist properties, provide a solver or enable rendering; see
[the contract and staged follow-ups](docs/physical-material-cards.md).
## Library foundations (#785, #786, #787)
- Proposed phases cover offline bundled/project/user packs, physical materials and
appearances, editable wood mapping, and parameterized component/assembly families.
Stable identity/revision, copied design definitions, provenance and resource limits
apply from the first implementation. Existing material, appearance and standard-part
commands remain the application paths. See [the proposal](docs/book/src/dev/libraries.md)
for source/license review and acceptance gates. This is planning only: pack loading,
texture rendering and expanded component families are not implemented by this entry.
## Render foundation
- A bounded cooperative Radiance RGBE input utility is available in L1 render;
it does not yet enable HDRI/PBR rendering or a document import command. See
[the input contract](docs/render-hdr-input.md) and #813 for integration work.
- R1 adds renderer-independent backend/profile metadata, strict request resolution and
bounded image/scratch admission checks, exposed by the read-only `render.backends`
command. It describes the current CPU raster snapshot path without evaluating the model.
Existing snapshot and viewport routes remain unchanged. The Render workspace, HDRI/PBR,
render jobs and optional engines remain future work; see [the contract](docs/render-backends.md)
and [the phased proposal (#620)](https://github.com/bherbruck/solidcraft/issues/620).
## Dimensioning and error reporting
- MCP `execute` accepts `verbose: false`, sharing the compact reply helper used by
`batch`: body changes and created sketch IDs stay, while repeated lists are omitted.
Both tools retain full replies by default.
- Sketch Create groups drawing variants under Line, Rectangle, Circle, Arc, Slot,
Polygon and Spline. Each open part remembers its last interactive choice per family
across sketch sessions and document-tab switches; closing the part resets these UI choices.
- Inscribed, circumscribed and edge polygon tools preview their actual outline before
placement, including orientation and the selected side count.
- Construction (X) preserves the active sketch drawing tool, picked anchors and inline
dimension values, so line type can change while a shape is in progress.
- Planar sketch construction previews and hover feedback keep the same dashed pattern as
committed geometry; centerlines retain their dash-dot pattern.
- Point has a default Sketch CREATE toolbar button alongside the existing drawing tools.
Saved custom layouts are preserved; Reset Toolbar restores the default Point button.
- Construction planes cannot take an origin-plane name (XY, XZ, YZ, Top, Front, Right). Plane
arguments reject such a name when an older design's construction plane already has it. Use
the other alias, the construction feature's numeric ID or an explicit plane; existing
stored references keep their saved meaning. Section Analysis uses the same check.
- Sketch text can be edited from a selected outline's context menu or the Edit Text command.
- Sketch text can be edited from a letter interior or outline's context menu or the Edit Text command.
Interior selection requires every profile boundary to belong to the same text source;
the editor highlights the whole source without replacing profile selections.
Content, baseline X/Y, height and rotation update the original text source in one undo step;
cancelling or rejecting an invalid value preserves the sketch. Position and height fields
use the document units captured when editing starts; generated outlines remain associated
with their text source, and untouched inputs preserve their saved internal values.
Dragging a text outline moves the entire source in one undo step; Escape restores the
gesture. Move/Copy on a selected text outline provides unit-aware X/Y distances and rotation
about its baseline; copies keep an independent editable text source.
- Repeating the initial point of a planar multi-point sketch drawing tool is neutral: the tool
waits for a distinct point without a degenerate-shape error or history entry. Typed input
and programmatic commands retain their validation.
@@ -145,6 +196,9 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
## View navigation
- Desktop Ctrl/Cmd +/- and 0 change or reset the saved UI scale. The scale slider keeps
its layout stable during a drag and applies the selected scale on release.
- OpenSCAD navigation preset: left drag orbits, right/middle drag pans, Shift+right/middle
drag zooms, and the wheel zooms. Available in the shared desktop/web Navigation preferences.
- Compact clickable signed X/Y/Z axis tips, smooth drag-to-orbit, and Shift-orbit snapping
@@ -154,8 +208,16 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
## Known limitations
- Scheduled and manual CI scan locked dependency advisories, bans and sources with
cargo-deny. Vulnerabilities fail the scan; unmaintained crates warn. Local vendored
patches require separate upstream review because advisory databases cannot version-match
their path dependencies. Pull requests retain the existing licence checks.
- Associative shell-face sketches require a unique certified planar source. Nested shell
attachments and ambiguous coincident sources remain unsupported.
- G2 fillets: exact quintic blends of straight edges between planar faces, with one perpendicular
planar end face at each end. The UI and `solid.fillet` expose `curvature` (alias `g2`); the size
is an equivalent circular setback. Curved faces/edges, sloped ends and corner junctions remain unsupported.
- Fillets and chamfers: constant, variable-radius and chord-length fillets; a radius per edge,
rounds next to earlier rounds of another radius, and smooth corner patches where three rounds
meet (a sphere for equal radii); straight edges (convex or concave) between planar faces with perpendicular
@@ -173,7 +235,14 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
edges between any two smooth sides (a branch pipe on a main pipe), by a rolling ball (the
blend a rational B-spline through exact arcs), closed or ending at planar faces square to the
edges, where straight or curved cap edges are trimmed to the contacts (the straight edge
between two overlapping extruded arcs).
between two overlapping extruded arcs); knife edges, where the two faces have opposite
normals (a wall that runs out tangent into a face: the ball that fits there); an edge that
runs out along an end face tangent to one side (the arc top edge of that wall), cut where the
blend meets it; rounds that end in an earlier round on curved faces, in any order (the rounds
of two edges at a vertex meet in a crease, as on a box), picked together or one after the
other, except a round after a round of the edge between the wall and a flat face that it
would run over; the three edges at a vertex of any smooth faces, of one radius, end in a
piece of sphere.
- Booleans: prisms along one direction (plates with holes, slots, patterns of them, equal
sections stacked end to end) are combined exactly in 2D. Qualified axial cuts preserve
annular holes with overrun cutters, including inward Press Pull after a joined extrusion;
@@ -209,8 +278,12 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
Sweeps along 3D sketch curves follow their polyline (smooth walls when the path is smooth).
- Extend intersects exact ellipses and rational/high-degree splines without moving the line or
circular support. Associative free-form offsets have a whole-span 0.002 mm normal-distance
certificate and refine within bounded node limits while retaining existing IDs. Singular,
self-intersecting and ambiguous curves refuse atomically; mixed junctions and nonsmooth
certificate and refine within bounded node limits while retaining existing IDs.
At the node limit, geometric certificate subdivisions accommodate unequal spline parameter
speeds without changing stored nodes or relaxing the distance tolerance (#971).
Associated offset fit nodes stay hidden from display, picking and snapping; dragging the
editable source updates the derived boundary live. Break Association restores point editing.
Singular, self-intersecting and ambiguous curves refuse atomically; mixed junctions and nonsmooth
closed natural-spline seams remain limitations. New explicitly closed fit splines and closed
offset outputs use periodic C2 interpolation, supporting repeated certified offsets; older
natural splines are preserved. Clicking the starting fit point deliberately closes a new loop.
@@ -285,6 +358,8 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
record their coordinate frame; legacy wires migrate from known provenance without changing
coordinates. Broken legacy links with missing provenance can require explicit refresh or relink.
See [occurrence workflows](docs/occurrence-workflows.md).
- Toolbar drops on panel labels, gaps and margins append commands with insertion feedback;
button drops keep insert-before ordering.
- Parameters and Configurations have distinct actions in the SOLID toolbar's CONFIGURE panel.
Parameters opens the user/model variable editor; Configurations opens the configuration table.
Configure remains available in the panel dropdown.
@@ -317,6 +392,12 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings.
exact holes and retained islands, including the union of multiple selected profiles, and
records complementary depth layers as pockets while retaining the stock gauge. Unsupported
tapered or bend-crossing machining refuses atomically instead of restoring material later.
Sketches attach to a sheet's flat faces (base and flanges, either side) by panel, in the
flat pattern's coordinates, and follow it through rebuilds and unfold/refold.
A flange on another flange's far edge (a return) follows that flange's closed end and keeps
clear of the flange beside it, and the returns of one feature are mitred toward each other
with the rule's gap where their flanges meet at a corner; returns of separate features, and
flanges on returns, are not closed toward each other.
- Rolling-ball fillet accuracy: independent offset-cylinder tee references check surface
distance, actual trimmed tessellation, supporting-cylinder contacts and tangency at two
scales. The measured trimmed-part error is below 0.0001 mm; this is an analytic geometry
@@ -350,13 +431,21 @@ and reference-object placement remain separate work, as do loft rails and center
- Design containers (#303, phase one) separate and deduplicate STEP, raw images and
lossless binary mesh arrays while keeping plain JSON as a supported Save As/export
format. Generated-mesh storage measurements reduce serialization and decoding work;
evaluated geometry caching and instant cold display remain separate follow-up work.
a seekable reader defers original blobs until after provisional display.
See [container format](docs/design-container.md) and [performance measurements](docs/perf.md).
- Desktop Open can show an experimental, opt-in bounded saved display preview while rebuilding in the existing
file worker. Ordinary saves omit display caches; explicit `preview: true` saves cache
only already warm meshes. Document hashes and application versions reject
stale previews. Preview geometry cannot be edited or measured, and cancelled or stale
completions cannot replace a newer document. Evaluated B-rep restoration remains open.
- Warm saves with explicit `cache: true` can include a versioned native B-rep cache through SolidCraft-owned topology
and geometry records, with a separate truck adapter and bounded binary reader. Exact
face-source certificates and operation history survive codec round trips. A provisional
picture can be generated before original blobs are read; editing and measurement wait
for authoritative reconstruction. Cached metadata binds preview and reconstruction to
the same document despite concurrent file writes. Default saves omit this cache and never force cold
evaluation or meshing; explicit `preview: true` remains a separate warm-mesh option.
Keys, resource budgets and document/revision guards reject incompatible, hostile or
stale results. A second modelling kernel still requires its own tested adapter.
- Warm saves with explicit `cache: true` can also carry a bounded PNG thumbnail from already cached meshes.
Home reads it without rebuilding the design when its local picture is missing;
mismatched or malformed thumbnails fall back to normal generation. The image is
kernel-independent and saving never forces cold meshing to produce it.
- Cold rebuild profiling separates parsing, geometry, placement, display meshing and
thumbnails. Straight-line closest-point projection and exact curved-surface evaluation
+32
View File
@@ -0,0 +1,32 @@
# Security policy
## Supported versions
SolidCraft is young and moves quickly, so only the latest release gets security fixes. Please
update to the [latest release](https://github.com/bherbruck/solidcraft/releases/latest) before
reporting, and check whether the problem is still on `main`.
| Version | Supported |
| --- | --- |
| Latest release | ✅ |
| Nightly builds | ✅ (fixes land here first) |
| Older releases | ❌ |
## Reporting a vulnerability
Please **don't open a public issue** for a security problem. Report it privately through GitHub:
open the repository's [Security tab](https://github.com/bherbruck/solidcraft/security) and choose
**Report a vulnerability**
([direct link](https://github.com/bherbruck/solidcraft/security/advisories/new)).
Please include what you found, how to reproduce it (a design file, command or MCP request), the
version and platform, and the impact you expect. I'll acknowledge the report as soon as I can,
keep you updated in the advisory, and credit you when the fix is published unless you'd rather
stay anonymous.
Areas where reports are especially useful:
- opening untrusted design, STEP, STL or 3MF files (crashes, excessive memory or CPU, path tricks)
- the MCP server and the control API (file access outside the allowed folders, command injection)
- the web app (anything reaching beyond the page's own storage)
- release packages and the update paths (AppImage, Flatpak, MSI, deb, rpm, snap)
+4 -5
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 454 crates below.
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 453 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.
@@ -262,7 +262,6 @@ proc-macro2 1.0.107 (MIT OR Apache-2.0)
profiling 1.0.18 (MIT OR Apache-2.0)
pxfm 0.1.30 (BSD-3-Clause OR Apache-2.0)
quick-error 2.0.1 (MIT/Apache-2.0)
quick-xml 0.38.4 (MIT)
quick-xml 0.41.0 (MIT)
quote 1.0.47 (MIT OR Apache-2.0)
r-efi 5.3.0 (MIT OR Apache-2.0 OR LGPL-2.1-or-later)
@@ -8709,7 +8708,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================================================
quick-xml 0.38.4, quick-xml 0.41.0
quick-xml 0.41.0
================================================================================
The MIT License (MIT)
@@ -13350,11 +13349,11 @@ truck-topology 0.6.0: Apache-2.0 (https://github.com/ricosjp/truck)
Bundled fonts (SIL Open Font License, Version 1.1)
================================================================================
- LiberationSans-Regular.ttf (crates/sketch/fonts/LiberationSans-Regular.ttf)
- LiberationSans-Regular.ttf (assets/fonts/liberation-sans/LiberationSans-Regular.ttf)
Copyright 2012 Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google)
Source: https://github.com/liberationfonts
- BIZUDPGothic-Regular.ttf (apps/solidcraft-web/fonts/BIZUDPGothic-Regular.ttf)
- BIZUDPGothic-Regular.ttf (assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf)
Copyright 2022 The BIZ UDGothic Project Authors (https://github.com/googlefonts/morisawa-biz-ud-mincho)
Source: https://github.com/googlefonts/morisawa-biz-ud-gothic
+29 -6
View File
@@ -9,7 +9,8 @@
//! 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] MCP server on stdio (docs/mcp.md)
//! solidcraft-cli mcp [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
//! MCP server on stdio (docs/mcp.md)
//! solidcraft-cli licences SolidCraft's licence and the third-party licences
//! solidcraft-cli icon --out F.png|F.ico [--size N] the app icon (packaging; drawn, not a file)
//! ```
@@ -25,7 +26,7 @@ mod spatial;
use std::process::ExitCode;
use serde_json::{Value, json};
#[path = "../../app_icon.rs"]
#[path = "../../../assets/icons/app/shared/source/app_icon.rs"]
mod app_icon;
use solidcraft_engine::Session;
@@ -43,7 +44,7 @@ const USAGE: &str = "usage:
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]
solidcraft-cli mcp [--in design.solidcraft|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]
solidcraft-cli licences
solidcraft-cli icon --out icon.png [--size 256] | --out icon.ico
@@ -312,11 +313,27 @@ fn cmd_exec(args: &[String]) -> Result<(), String> {
}
/// MCP server on stdio: headless (optionally starting from a design or script) or bridged to
/// a running app's control port. stdout carries only protocol messages.
/// 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 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)));
if let Some(s) = &scope {
let roots: Vec<String> = s.roots().iter().map(|r| r.display().to_string()).collect();
eprintln!(
"solidcraft mcp: files limited to {} and the open design's folder (--allow-path DIR adds one, --allow-any-path lifts the limit)",
roots.join(", ")
);
let design_dir = flag(args, "--in")
.and_then(|p| std::path::Path::new(p).parent())
.map(|d| if d.as_os_str().is_empty() { std::path::Path::new(".") } else { d });
if let Some(why) = design_dir.and_then(solidcraft_engine::io::vfs::design_folder_refusal) {
eprintln!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
}
}
let backend: Box<dyn solidcraft_mcp::Backend> = match (flag(args, "--connect"), flag(args, "--in")) {
(Some(_), Some(_)) => return Err("mcp: use either --connect or --in, not both".into()),
(Some(addr), None) => Box::new(solidcraft_mcp::Remote::connect(addr).map_err(|e| {
(Some(addr), None) => Box::new(solidcraft_mcp::Remote::connect_with_token(addr, flag(args, "--token")).map_err(|e| {
let port = addr.rsplit(':').next().filter(|p| p.parse::<u16>().is_ok()).unwrap_or("PORT");
format!(
"cannot connect to {addr}: {e}\nno SolidCraft app is listening there; start it with \
@@ -328,7 +345,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
};
let stdin = std::io::stdin();
let stdout = std::io::stdout();
solidcraft_mcp::Server::new(backend).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
solidcraft_mcp::Server::new(backend).with_path_scope(scope).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
}
/// `save-stress <path> <rounds>`: save a design of a few hundred kilobytes `rounds` times, its
@@ -393,10 +410,13 @@ fn cmd_bench_open(args: &[String]) -> Result<(), String> {
let read_ms = t.elapsed().as_secs_f64() * 1000.0;
println!("{}", json!({"step": "read", "ms": read_ms, "bytes": bytes.len()}));
let t = std::time::Instant::now();
// The file as read, held until the timeline has evaluated it (as the import command does).
let mut read = None;
let doc = if step {
let imported = solidcraft_engine::io::step_import_feature(&bytes, path).map_err(|e| e.to_string())?;
let mut doc = solidcraft_engine::doc::Document::new(&imported.name);
doc.add_feature(imported.kind, Some(&imported.name)).map_err(|e| e.to_string())?;
read = Some(imported.import);
doc
} else {
solidcraft_engine::io::read_design(&bytes).map_err(|e| e.to_string())?
@@ -408,12 +428,15 @@ fn cmd_bench_open(args: &[String]) -> Result<(), String> {
}
let t = std::time::Instant::now();
let s = Session::new(doc);
drop(read);
println!("{}", json!({"step": "evaluate", "ms": t.elapsed().as_secs_f64() * 1000.0}));
for r in &s.model.results {
println!("{}", json!({"step": "feature", "id": r.id, "name": r.name, "ms": r.ms, "error": r.error}));
}
let t = std::time::Instant::now();
let st = s.world_state();
// As the desktop app prepares a design it opened.
st.warm_meshes(|| true);
let triangles: usize = st.bodies.iter().map(|b| b.mesh().triangles.len()).sum();
println!("{}", json!({"step": "mesh", "ms": t.elapsed().as_secs_f64() * 1000.0, "bodies": st.bodies.len(), "triangles": triangles}));
Ok(())
+17 -1
View File
@@ -514,7 +514,23 @@ pub fn to_script(recipe: &Value) -> Result<Value, String> {
out.push(json!({"command": "solid.mirror", "params": p}));
}
Some("fillet") => {
out.push(json!({"command": "solid.fillet", "params": {"edges": edge_points(f), "radius": f.get("radius"), "name": name}}))
let mut p = json!({"edges": edge_points(f), "radius": f.get("radius"), "name": name});
// Constant radius (the default), chord length, or a radius varying along each edge
// (`radius` at the end nearest `start`, `radius2` at the other, `mid` between,
// `smooth`), or a B-spline along each edge (`radius` its first control radius,
// `radii` the rest, `knots`); `fit` for a radius too large for the geometry.
match f.get("type").and_then(Value::as_str) {
Some("chord_length") => p["type"] = json!("chord"),
Some("variable_radius") => p["type"] = json!("variable"),
Some("spline_radius") => p["type"] = json!("spline"),
_ => {}
}
for key in ["radius2", "start", "mid", "smooth", "fit", "radii", "knots"] {
if let Some(v) = f.get(key) {
p[key] = v.clone();
}
}
out.push(json!({"command": "solid.fillet", "params": p}))
}
Some("chamfer") => {
out.push(json!({"command": "solid.chamfer", "params": {"edges": edge_points(f), "distance": f.get("distance"), "name": name}}))
File diff suppressed because it is too large. Load diff
+8 -2
View File
@@ -16,7 +16,13 @@ hostile variants of it never panic.
| `modern-path-pattern` | bd98035 | pattern on path (oracle 53) |
| `modern-plastic` | bd98035 | shell, lip, boss, rest, snap fit, plastic rule |
| `naming-split-pieces` | 9a979cf | format 13 names: a slot splits the top; fillet, chamfer and offset face name its pieces by number (`F2:top#2`) |
| `move-bodies` | 00baf1d | `solid.move` before it could copy: two bodies, one translated in place, one translated and turned 90 deg about Z (no `copy` key anywhere in the file) |
| `split-body-pieces` | v0.3.2 (07e263d) | a slot along X cuts a block into two bodies; a fillet, a chamfer and an appearance refer to the pieces by body name (`Body1`, `Body1 (1)`) |
| `issue-489-stacked-join` | v0.6.0 (809ecf7), desktop | a 70 × 84 extrude, then a 70 × 62 strip joined on its top face, sketched partly over the face's projected edges (issue #489; expected values from main at 2880525) |
| `issue-616-lid-ribs` | v0.7.0 (a9879fa), CLI | a rounded lid with a skirt ring, a window and a slot cut through, then four ribs joined on the plate over the skirt, flush with the window's sides; v0.7.0 displaced the ribs and left a slit through the skirt and plate (issue #616; script `examples/regressions/lid_ribs_flush_with_window.json`, expected values from the fix) |
The scripts that made them are in `scripts/`. Never regenerate a file with a newer build: the
point is that it was written by the old one. Add new files for new eras instead.
The scripts that made them are in `scripts/` (`issue-489-stacked-join` was drawn in the desktop
app and has none; `issue-616-lid-ribs` is made by its regression script). Every flat face of every final body must also take an attached sketch and an
offset plane; the faces that can't yet are listed with their issues in `design_corpus.rs`.
Never regenerate a file with a newer build: the point is that it was written by the old one. Add new files for new eras instead.
@@ -0,0 +1,157 @@
{
"bodies": [
{
"area_mm2": 25751.999999999945,
"bbox": {
"max": [
35.0,
42.0,
48.00000000000001
],
"min": [
-35.0,
-42.000000000000014,
0.0
]
},
"center_of_mass": [
5.524909928104648e-14,
-1.0945382096961234e-12,
22.366336633663238
],
"density_g_cm3": 1.29,
"edges": 24,
"face_types": {
"plane": 10
},
"faces": 10,
"kernel": {
"edges": 24,
"faces": 10,
"shells": 1,
"vertices": 16
},
"mass_g": 328.33079999999734,
"material": "Default",
"name": "Body1",
"vertices": 16,
"volume_mm3": 254519.9999999979
}
],
"body_count": 1,
"params": [
{
"comment": "",
"error": null,
"expression": "84",
"model": true,
"name": "d1",
"unit": "mm",
"value": 84.0
},
{
"comment": "",
"error": null,
"expression": "70",
"model": true,
"name": "d2",
"unit": "mm",
"value": 70.0
},
{
"comment": "",
"error": null,
"expression": "62",
"model": true,
"name": "d4",
"unit": "mm",
"value": 62.0
}
],
"sketches": [
{
"curves": 6,
"dof": 0,
"id": 1,
"name": "Sketch1",
"profiles": 1,
"status": "solved",
"visible": false
},
{
"curves": 10,
"dof": 1,
"id": 3,
"name": "Sketch2",
"profiles": 3,
"status": "solved",
"visible": false
}
],
"timeline": [
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 1,
"ms": 0.131943,
"name": "Sketch1",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 2,
"ms": 1.0812709999999999,
"name": "Extrude1",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 3,
"ms": 0.450002,
"name": "Sketch2",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 4,
"ms": 5.191514000000001,
"name": "Extrude2",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
}
],
"total": {
"area_mm2": 25751.999999999945,
"edges": 24,
"faces": 10,
"mass_g": 328.33079999999734,
"vertices": 16,
"volume_mm3": 254519.9999999979
}
}
@@ -0,0 +1,539 @@
{
"features": [
{
"id": 1,
"name": "Sketch1",
"plane": {
"name": "XY",
"type": "origin"
},
"sketch": {
"constraints": [
{
"id": "k1",
"l": 0,
"type": "horizontal"
},
{
"id": "k2",
"l": 2,
"type": "horizontal"
},
{
"id": "k3",
"l": 1,
"type": "vertical"
},
{
"id": "k4",
"l": 3,
"type": "vertical"
},
{
"id": "k5",
"l": 4,
"p": 5,
"type": "midpoint"
},
{
"id": "k6",
"p": 0,
"q": 5,
"type": "coincident"
},
{
"id": "k7",
"l": 1,
"param": "d1",
"type": "length",
"value": 84.0
},
{
"id": "k8",
"l": 0,
"param": "d2",
"type": "length",
"value": 70.0
}
],
"counters": {
"k": 8,
"l": 6,
"p": 5
},
"curves": [
{
"a": 1,
"b": 2,
"construction": false,
"id": "l1",
"type": "line"
},
{
"a": 2,
"b": 3,
"construction": false,
"id": "l2",
"type": "line"
},
{
"a": 3,
"b": 4,
"construction": false,
"id": "l3",
"type": "line"
},
{
"a": 4,
"b": 1,
"construction": false,
"id": "l4",
"type": "line"
},
{
"a": 1,
"b": 3,
"construction": true,
"id": "l5",
"type": "line"
},
{
"a": 2,
"b": 4,
"construction": true,
"id": "l6",
"type": "line"
}
],
"points": [
{
"fixed": true,
"id": "origin",
"pos": [
0.0,
0.0
]
},
{
"fixed": false,
"id": "p1",
"pos": [
-35.0,
-42.0
]
},
{
"fixed": false,
"id": "p2",
"pos": [
35.0,
-42.0
]
},
{
"fixed": false,
"id": "p3",
"pos": [
35.0,
42.0
]
},
{
"fixed": false,
"id": "p4",
"pos": [
-35.0,
42.0
]
},
{
"fixed": false,
"id": "p5",
"pos": [
0.0,
0.0
]
}
]
},
"suppressed": false,
"type": "sketch"
},
{
"extent": {
"direction": "positive",
"distance": "30"
},
"id": 2,
"name": "Extrude1",
"operation": "new_body",
"param_names": [
"d3"
],
"profiles": {
"loops": [
[
"l1",
"l2",
"l3",
"l4"
]
],
"type": "curves"
},
"sketch": 1,
"suppressed": false,
"type": "extrude"
},
{
"id": 3,
"name": "Sketch2",
"plane": {
"anchor": {
"author_path": [],
"component": 0,
"coordinates": [
0.0,
0.0
],
"path": [],
"target": {
"body": "Body1",
"certificate": {
"high": true,
"holes": [],
"kind": "extrude_cap",
"outer": [
"l1",
"l2",
"l3",
"l4"
],
"sketch": 1
},
"name": "F2:cap:high:ed6121d9bed14d3fd103f347231990d103349f33b7874ad292d3c4d3539266c3",
"origin_feature": 2,
"point": [
0.0,
0.0,
30.0
]
},
"x_axis": [
1.0,
0.0
]
},
"at": [
2.012802880253332,
7.4635742926698185,
30.000000000000114
],
"name": "F2:top",
"plane": {
"origin": [
0.0,
0.0,
30.0
],
"x_dir": [
1.0,
0.0,
0.0
],
"y_dir": [
0.0,
1.0,
0.0
]
},
"type": "face"
},
"sketch": {
"constraints": [
{
"id": "k1",
"l": 4,
"type": "horizontal"
},
{
"id": "k2",
"l": 6,
"type": "horizontal"
},
{
"id": "k3",
"l": 5,
"type": "vertical"
},
{
"id": "k4",
"l": 7,
"type": "vertical"
},
{
"id": "k5",
"l": 8,
"p": 9,
"type": "midpoint"
},
{
"id": "k6",
"p": 0,
"q": 9,
"type": "coincident"
},
{
"id": "k7",
"p": 7,
"param": "d4",
"q": 6,
"text": [
-0.2,
9.200000000000003
],
"type": "distance",
"value": 62.0
}
],
"counters": {
"j": 1,
"k": 7,
"l": 10,
"p": 9
},
"curves": [
{
"a": 1,
"b": 2,
"construction": false,
"id": "l1",
"link": "j1",
"type": "line"
},
{
"a": 3,
"b": 1,
"construction": false,
"id": "l2",
"link": "j1",
"type": "line"
},
{
"a": 2,
"b": 4,
"construction": false,
"id": "l3",
"link": "j1",
"type": "line"
},
{
"a": 4,
"b": 3,
"construction": false,
"id": "l4",
"link": "j1",
"type": "line"
},
{
"a": 5,
"b": 6,
"construction": false,
"id": "l5",
"type": "line"
},
{
"a": 6,
"b": 7,
"construction": false,
"id": "l6",
"type": "line"
},
{
"a": 7,
"b": 8,
"construction": false,
"id": "l7",
"type": "line"
},
{
"a": 8,
"b": 5,
"construction": false,
"id": "l8",
"type": "line"
},
{
"a": 5,
"b": 7,
"construction": true,
"id": "l9",
"type": "line"
},
{
"a": 6,
"b": 8,
"construction": true,
"id": "l10",
"type": "line"
}
],
"links": [
{
"auto_face": true,
"face_name": "F2:cap:high:ed6121d9bed14d3fd103f347231990d103349f33b7874ad292d3c4d3539266c3",
"id": "j1",
"kind": "project",
"source": {
"at": [
11.666666666666666,
-13.999999999999995,
30.0
],
"body": "Body1",
"type": "face"
}
}
],
"points": [
{
"fixed": true,
"id": "origin",
"pos": [
0.0,
0.0
]
},
{
"fixed": true,
"id": "p1",
"link": "j1",
"pos": [
-35.0,
-42.0
]
},
{
"fixed": true,
"id": "p2",
"link": "j1",
"pos": [
-35.0,
42.0
]
},
{
"fixed": true,
"id": "p3",
"link": "j1",
"pos": [
35.0,
-42.0
]
},
{
"fixed": true,
"id": "p4",
"link": "j1",
"pos": [
35.0,
42.0
]
},
{
"fixed": false,
"id": "p5",
"pos": [
-35.0,
-31.000000000002185
]
},
{
"fixed": false,
"id": "p6",
"pos": [
35.0,
-31.00000000000349
]
},
{
"fixed": false,
"id": "p7",
"pos": [
35.0,
30.99999999999603
]
},
{
"fixed": false,
"id": "p8",
"pos": [
-35.0,
30.999999999995318
]
},
{
"fixed": false,
"id": "p9",
"pos": [
0.0,
0.0
]
}
]
},
"suppressed": false,
"type": "sketch"
},
{
"extent": {
"direction": "positive",
"distance": "18"
},
"id": 4,
"name": "Extrude2",
"operation": "join",
"param_names": [
"d5"
],
"profiles": {
"loops": [
[
"l1",
"l5",
"l4",
"l7"
]
],
"type": "curves"
},
"sketch": 3,
"suppressed": false,
"type": "extrude"
}
],
"format": "solidcraft/14",
"name": "Untitled",
"next_id": 6,
"params": [
{
"expr": "84",
"model": true,
"name": "d1",
"unit": "mm"
},
{
"expr": "70",
"model": true,
"name": "d2",
"unit": "mm"
},
{
"expr": "62",
"model": true,
"name": "d4",
"unit": "mm"
}
],
"units": "mm"
}
@@ -0,0 +1,227 @@
{
"bodies": [
{
"area_mm2": 8430.29544246263,
"bbox": {
"max": [
176.8000000000002,
89.30000000000001,
14.100000000000001
],
"min": [
99.99999999999991,
-1.820765760385257e-14,
0.0
]
},
"center_of_mass": [
138.3999999716193,
51.04392591016203,
2.0990634391586322
],
"density_g_cm3": 1.29,
"edges": 156,
"face_types": {
"cylinder": 4,
"plane": 50
},
"faces": 54,
"kernel": {
"edges": 156,
"faces": 54,
"shells": 1,
"vertices": 104
},
"mass_g": 9.101726874183207,
"material": "Default",
"name": "Lid",
"vertices": 104,
"volume_mm3": 7055.602228048997
}
],
"body_count": 1,
"params": [],
"sketches": [
{
"curves": 8,
"dof": 8,
"id": 3,
"name": "Skirt",
"profiles": 2,
"status": "solved",
"visible": false
}
],
"timeline": [
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 1,
"ms": 1.178957,
"name": "Box1",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 2,
"ms": 83.24342399999999,
"name": "Fillet1",
"rolled_back": false,
"suppressed": false,
"type": "FilletFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 3,
"ms": 0.150889,
"name": "Skirt",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 4,
"ms": 94.465481,
"name": "Extrude1",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 5,
"ms": 50.426736,
"name": "Box2",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 6,
"ms": 40.383005,
"name": "Box3",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 7,
"ms": 59.041461000000005,
"name": "Box4",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [
{
"face_shift_mm": 0.0,
"rotation_rad": 0.0,
"strategy": "planar_fallback",
"tolerance_mm": 0.0,
"translation_mm": 0.0
}
],
"truncated": false
},
"error": null,
"id": 8,
"ms": 562.5477970000001,
"name": "Rib1",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": "Boolean retry: exact planar fallback (no displacement)"
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 9,
"ms": 89.56076,
"name": "Rib2",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 10,
"ms": 65.330634,
"name": "Rib3",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 11,
"ms": 97.229403,
"name": "Rib4",
"rolled_back": false,
"suppressed": false,
"type": "BoxFeature",
"warning": null
}
],
"total": {
"area_mm2": 8430.29544246263,
"edges": 156,
"faces": 54,
"mass_g": 9.101726874183207,
"vertices": 104,
"volume_mm3": 7055.602228048997
}
}
@@ -0,0 +1,507 @@
{
"features": [
{
"body_names": [
"Lid"
],
"corner": [
100.0,
0.0,
0.0
],
"height": "2.4",
"id": 1,
"length": "76.8",
"name": "Box1",
"operation": "new_body",
"param_names": [
"d1",
"d2",
"d3"
],
"suppressed": false,
"type": "box",
"width": "89.3"
},
{
"edge_names": [
"F1:-x|F1:-y",
"F1:+x|F1:-y",
"F1:+y|F1:-x",
"F1:+x|F1:+y"
],
"edge_refs": [
{
"dir": [
0.0,
0.0,
-1.0
],
"max": [
176.8,
89.3,
2.4
],
"min": [
100.0,
0.0,
0.0
]
},
{
"dir": [
0.0,
0.0,
1.0
],
"max": [
176.8,
89.3,
2.4
],
"min": [
100.0,
0.0,
0.0
]
},
{
"dir": [
0.0,
0.0,
1.0
],
"max": [
176.8,
89.3,
2.4
],
"min": [
100.0,
0.0,
0.0
]
},
{
"dir": [
0.0,
0.0,
1.0
],
"max": [
176.8,
89.3,
2.4
],
"min": [
100.0,
0.0,
0.0
]
}
],
"edges": [
[
100.0,
0.0,
1.2
],
[
176.8,
0.0,
1.2
],
[
100.0,
89.3,
1.2
],
[
176.8,
89.3,
1.2
]
],
"id": 2,
"name": "Fillet1",
"param_names": [
"d4"
],
"radius": "3",
"suppressed": false,
"type": "fillet"
},
{
"id": 3,
"name": "Skirt",
"param_names": [
"d5"
],
"plane": {
"base": {
"name": "XY",
"type": "origin"
},
"distance": "2.4",
"type": "offset"
},
"sketch": {
"constraints": [
{
"id": "k1",
"l": 0,
"type": "horizontal"
},
{
"id": "k2",
"l": 2,
"type": "horizontal"
},
{
"id": "k3",
"l": 1,
"type": "vertical"
},
{
"id": "k4",
"l": 3,
"type": "vertical"
},
{
"id": "k5",
"l": 4,
"type": "horizontal"
},
{
"id": "k6",
"l": 6,
"type": "horizontal"
},
{
"id": "k7",
"l": 5,
"type": "vertical"
},
{
"id": "k8",
"l": 7,
"type": "vertical"
}
],
"counters": {
"k": 8,
"l": 8,
"p": 8
},
"curves": [
{
"a": 1,
"b": 2,
"construction": false,
"id": "l1",
"type": "line"
},
{
"a": 2,
"b": 3,
"construction": false,
"id": "l2",
"type": "line"
},
{
"a": 3,
"b": 4,
"construction": false,
"id": "l3",
"type": "line"
},
{
"a": 4,
"b": 1,
"construction": false,
"id": "l4",
"type": "line"
},
{
"a": 5,
"b": 6,
"construction": false,
"id": "l5",
"type": "line"
},
{
"a": 6,
"b": 7,
"construction": false,
"id": "l6",
"type": "line"
},
{
"a": 7,
"b": 8,
"construction": false,
"id": "l7",
"type": "line"
},
{
"a": 8,
"b": 5,
"construction": false,
"id": "l8",
"type": "line"
}
],
"points": [
{
"fixed": true,
"id": "origin",
"pos": [
0.0,
0.0
]
},
{
"fixed": false,
"id": "p1",
"pos": [
102.65,
2.65
]
},
{
"fixed": false,
"id": "p2",
"pos": [
174.15,
2.65
]
},
{
"fixed": false,
"id": "p3",
"pos": [
174.15,
86.65
]
},
{
"fixed": false,
"id": "p4",
"pos": [
102.65,
86.65
]
},
{
"fixed": false,
"id": "p5",
"pos": [
104.15,
4.15
]
},
{
"fixed": false,
"id": "p6",
"pos": [
172.65,
4.15
]
},
{
"fixed": false,
"id": "p7",
"pos": [
172.65,
85.15
]
},
{
"fixed": false,
"id": "p8",
"pos": [
104.15,
85.15
]
}
]
},
"suppressed": false,
"type": "sketch"
},
{
"extent": {
"direction": "positive",
"distance": "3"
},
"id": 4,
"name": "Extrude1",
"operation": "join",
"param_names": [
"d6"
],
"profiles": {
"loops": [
[
"l1",
"l2",
"l3",
"l4"
]
],
"type": "curves"
},
"sketch": 3,
"suppressed": false,
"targets": [
"Lid"
],
"type": "extrude"
},
{
"corner": [
104.4,
5.4,
-1.0
],
"height": "8",
"id": 5,
"length": "68",
"name": "Box2",
"operation": "cut",
"param_names": [
"d7",
"d8",
"d9"
],
"suppressed": false,
"type": "box",
"width": "61"
},
{
"corner": [
116.4,
72.0,
-1.0
],
"height": "8",
"id": 6,
"length": "44",
"name": "Box3",
"operation": "cut",
"param_names": [
"d10",
"d11",
"d12"
],
"suppressed": false,
"type": "box",
"width": "10"
},
{
"corner": [
127.4,
84.0,
2.4
],
"height": "4",
"id": 7,
"length": "22",
"name": "Box4",
"operation": "cut",
"param_names": [
"d13",
"d14",
"d15"
],
"suppressed": false,
"type": "box",
"width": "6"
},
{
"corner": [
102.8,
14.0,
2.4
],
"height": "11.7",
"id": 8,
"length": "1.6",
"name": "Rib1",
"operation": "join",
"param_names": [
"d16",
"d17",
"d18"
],
"suppressed": false,
"type": "box",
"width": "6"
},
{
"corner": [
102.8,
48.0,
2.4
],
"height": "11.7",
"id": 9,
"length": "1.6",
"name": "Rib2",
"operation": "join",
"param_names": [
"d19",
"d20",
"d21"
],
"suppressed": false,
"type": "box",
"width": "6"
},
{
"corner": [
172.4,
14.0,
2.4
],
"height": "11.7",
"id": 10,
"length": "1.6",
"name": "Rib3",
"operation": "join",
"param_names": [
"d22",
"d23",
"d24"
],
"suppressed": false,
"type": "box",
"width": "6"
},
{
"corner": [
172.4,
48.0,
2.4
],
"height": "11.7",
"id": 11,
"length": "1.6",
"name": "Rib4",
"operation": "join",
"param_names": [
"d25",
"d26",
"d27"
],
"suppressed": false,
"type": "box",
"width": "6"
}
],
"format": "solidcraft/15",
"name": "issue-616-lid-ribs",
"next_id": 12,
"params": [],
"units": "mm"
}
@@ -0,0 +1,194 @@
{
"bodies": [
{
"area_mm2": 2400.0,
"bbox": {
"max": [
50.0,
20.0,
20.0
],
"min": [
30.0,
0.0,
0.0
]
},
"center_of_mass": [
40.0,
10.0,
10.0
],
"density_g_cm3": 1.29,
"edges": 12,
"face_types": {
"plane": 6
},
"faces": 6,
"kernel": {
"edges": 12,
"faces": 6,
"shells": 1,
"vertices": 8
},
"mass_g": 10.32,
"material": "Default",
"name": "Block",
"vertices": 8,
"volume_mm3": 8000.0
},
{
"area_mm2": 600.0,
"bbox": {
"max": [
3.552713678800501e-15,
90.0,
10.0
],
"min": [
-9.999999999999998,
80.0,
0.0
]
},
"center_of_mass": [
-4.999999999999999,
85.0,
5.000000000000001
],
"density_g_cm3": 1.29,
"edges": 12,
"face_types": {
"plane": 6
},
"faces": 6,
"kernel": {
"edges": 12,
"faces": 6,
"shells": 1,
"vertices": 8
},
"mass_g": 1.29,
"material": "Default",
"name": "Peg",
"vertices": 8,
"volume_mm3": 1000.0
}
],
"body_count": 2,
"params": [],
"sketches": [
{
"curves": 4,
"dof": 2,
"id": 1,
"name": "Base",
"profiles": 1,
"status": "solved",
"visible": false
},
{
"curves": 4,
"dof": 4,
"id": 3,
"name": "Peg",
"profiles": 1,
"status": "solved",
"visible": false
}
],
"timeline": [
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 1,
"ms": 0.204585,
"name": "Base",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 2,
"ms": 2.318662,
"name": "Extrude1",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 3,
"ms": 0.109196,
"name": "Peg",
"rolled_back": false,
"suppressed": false,
"type": "Sketch",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 4,
"ms": 0.338859,
"name": "Extrude2",
"rolled_back": false,
"suppressed": false,
"type": "ExtrudeFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 5,
"ms": 0.312259,
"name": "Move1",
"rolled_back": false,
"suppressed": false,
"type": "MoveFeature",
"warning": null
},
{
"boolean_diagnostics": {
"events": [],
"truncated": false
},
"error": null,
"id": 6,
"ms": 0.23144699999999999,
"name": "Move2",
"rolled_back": false,
"suppressed": false,
"type": "MoveFeature",
"warning": null
}
],
"total": {
"area_mm2": 3000.0,
"edges": 24,
"faces": 12,
"mass_g": 11.61,
"vertices": 16,
"volume_mm3": 9000.0
}
}
@@ -0,0 +1 @@
{"features":[{"id":1,"name":"Base","plane":{"name":"XY","type":"origin"},"sketch":{"constraints":[{"id":"k1","l":0,"type":"horizontal"},{"id":"k2","l":2,"type":"horizontal"},{"id":"k3","l":1,"type":"vertical"},{"id":"k4","l":3,"type":"vertical"},{"id":"k5","p":0,"q":1,"type":"coincident"}],"counters":{"k":5,"l":4,"p":4},"curves":[{"a":1,"b":2,"construction":false,"id":"l1","type":"line"},{"a":2,"b":3,"construction":false,"id":"l2","type":"line"},{"a":3,"b":4,"construction":false,"id":"l3","type":"line"},{"a":4,"b":1,"construction":false,"id":"l4","type":"line"}],"points":[{"fixed":true,"id":"origin","pos":[0.0,0.0]},{"fixed":false,"id":"p1","pos":[0.0,0.0]},{"fixed":false,"id":"p2","pos":[20.0,0.0]},{"fixed":false,"id":"p3","pos":[20.0,20.0]},{"fixed":false,"id":"p4","pos":[0.0,20.0]}]},"suppressed":false,"type":"sketch"},{"body_names":["Block"],"extent":{"direction":"positive","distance":"20"},"id":2,"name":"Extrude1","operation":"new_body","param_names":["d1"],"profiles":{"type":"all"},"sketch":1,"suppressed":false,"type":"extrude"},{"id":3,"name":"Peg","plane":{"name":"XY","type":"origin"},"sketch":{"constraints":[{"id":"k1","l":0,"type":"horizontal"},{"id":"k2","l":2,"type":"horizontal"},{"id":"k3","l":1,"type":"vertical"},{"id":"k4","l":3,"type":"vertical"}],"counters":{"k":4,"l":4,"p":4},"curves":[{"a":1,"b":2,"construction":false,"id":"l1","type":"line"},{"a":2,"b":3,"construction":false,"id":"l2","type":"line"},{"a":3,"b":4,"construction":false,"id":"l3","type":"line"},{"a":4,"b":1,"construction":false,"id":"l4","type":"line"}],"points":[{"fixed":true,"id":"origin","pos":[0.0,0.0]},{"fixed":false,"id":"p1","pos":[40.0,0.0]},{"fixed":false,"id":"p2","pos":[50.0,0.0]},{"fixed":false,"id":"p3","pos":[50.0,10.0]},{"fixed":false,"id":"p4","pos":[40.0,10.0]}]},"suppressed":false,"type":"sketch"},{"body_names":["Peg"],"extent":{"direction":"positive","distance":"10"},"id":4,"name":"Extrude2","operation":"new_body","param_names":["d2"],"profiles":{"type":"all"},"sketch":3,"suppressed":false,"type":"extrude"},{"bodies":["Block"],"id":5,"name":"Move1","param_names":["d3","d4","d5"],"suppressed":false,"translate":["30","0","0"],"type":"move"},{"angle":"90 deg","bodies":["Peg"],"id":6,"name":"Move2","param_names":["d6","d7","d8","d9"],"rotate_axis":[0.0,0.0,1.0],"suppressed":false,"translate":["0","40","0"],"type":"move"}],"format":"solidcraft/15","name":"old-move-bodies","next_id":7,"params":[],"units":"mm"}
@@ -0,0 +1,14 @@
{
"commands": [
{"command": "sketch.create", "params": {"plane": "XY", "name": "Base"}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [0, 0], "p1": [20, 20]}},
{"command": "sketch.finish", "params": {}},
{"command": "solid.extrude", "params": {"distance": 20, "body_name": "Block"}},
{"command": "sketch.create", "params": {"plane": "XY", "name": "Peg"}},
{"command": "sketch.rectangle.two_point", "params": {"p0": [40, 0], "p1": [50, 10]}},
{"command": "sketch.finish", "params": {}},
{"command": "solid.extrude", "params": {"distance": 10, "operation": "new", "body_name": "Peg"}},
{"command": "solid.move", "params": {"bodies": ["Block"], "translate": [30, 0, 0]}},
{"command": "solid.move", "params": {"bodies": ["Peg"], "translate": [0, 40, 0], "axis": [0, 0, 1], "angle": "90 deg"}}
]
}
+131
View File
@@ -131,6 +131,137 @@ fn body_names_saved_before_exact_history_keep_their_pieces() {
assert!(s.doc.appearances.bodies.contains_key("Body1 (1)"));
}
/// Flat faces that can't take an attached sketch yet, by cause: (design, body, the faces'
/// planes as outward normal and distance from the origin, or every flat face of the body,
/// issue). Face indices are not used: they can change between builds. Each issue has an
/// ignored test below that checks its faces; a listed plane whose face attaches fails the main
/// check until it is removed from here.
type Planes = &'static [([f64; 3], f64)];
const NO_ATTACHMENT_YET: [(&str, &str, Option<Planes>, u32); 1] = [("issue-616-lid-ribs", "Lid", None, 628)];
/// The issue a face is listed under, and whether it is listed by its plane (not as every face).
fn known_without_attachment(design: &str, body: &str, normal: [f64; 3], distance: f64) -> Option<(u32, bool)> {
let same = |(n, d): &([f64; 3], f64)| (0..3).all(|i| (n[i] - normal[i]).abs() < 1e-6) && (d - distance).abs() < 1e-4;
NO_ATTACHMENT_YET.iter().find_map(|(d, b, planes, issue)| {
if *d != design || *b != body {
return None;
}
match planes {
None => Some((*issue, false)),
Some(planes) => planes.iter().any(same).then_some((*issue, true)),
}
})
}
fn undo_to(s: &mut Session, features: usize) {
for _ in 0..4 {
if s.doc.features.len() <= features {
return;
}
s.execute("edit.undo", &json!({})).unwrap();
}
assert_eq!(s.doc.features.len(), features, "undo did not remove the added feature");
}
/// Try a sketch and an offset plane on every flat face of every final body of every corpus
/// design, through the same face pick the desktop sends (body, face index, occurrence path, a
/// point on it), without `fixed`. Undo leaves the design as it was for the next face. Returns
/// (design, body, face, issue it is known under, what went wrong or None).
fn flat_face_attachments() -> Vec<(String, String, usize, Option<(u32, bool)>, Option<String>)> {
use solidcraft_engine::doc::{FaceAttachmentStatus, apply_point};
let mut out = Vec::new();
for p in corpus() {
let name = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
let mut s = open(&p);
let state = s.model.state();
let placed = s.placed_document();
let mut picks = Vec::new();
for (world, definition, component, path, frame) in placed.body_frames_with_source(&state) {
let Some(body) = state.bodies.iter().find(|b| b.definition_name() == definition && placed.model_body_component(b) == component) else {
continue;
};
let mesh = body.body.tessellate(0.01).unwrap();
for index in 0..body.body.face_count() {
if body.body.planar_face(index).is_none() {
continue;
}
// The middle of the face's largest triangle: well inside the face.
let point = mesh
.triangles
.iter()
.zip(&mesh.tri_face)
.filter(|(_, f)| **f as usize == index)
.filter_map(|(t, _)| mesh.tri(t))
.max_by(|[a, b, c], [d, e, f]| (*b - *a).cross(*c - *a).len().total_cmp(&(*e - *d).cross(*f - *d).len()))
.map(|[a, b, c]| (a + b + c) * (1.0 / 3.0));
let Some(point) = point else { continue };
let point = apply_point(&frame, point);
let normal =
body.body.planar_face(index).map(|plane| solidcraft_engine::doc::apply_vector(&frame, plane.normal())).unwrap_or_default();
let key = ([normal.x, normal.y, normal.z], normal.dot(point));
picks.push((world.clone(), index, key, json!({"face": point, "body": world, "index": index, "path": path})));
}
}
let before = s.doc.features.len();
for (body, index, (normal, distance), plane) in picks {
let mut problem = None;
match s.execute("sketch.create", &json!({"plane": plane})) {
Ok(created) => {
let id = created["sketch"].as_u64().unwrap_or(0);
match s.placed_document().sketch_face_attachment(id, &s.model) {
FaceAttachmentStatus::Healthy { .. } | FaceAttachmentStatus::Sheet { .. } => {}
other => problem = Some(format!("sketch attachment {other:?}")),
}
s.execute("sketch.finish", &json!({})).unwrap();
undo_to(&mut s, before);
}
Err(e) => problem = Some(format!("sketch.create: {e}")),
}
match s.execute("construct.plane.offset", &json!({"base": plane, "offset": 5})) {
Ok(_) => undo_to(&mut s, before),
Err(e) => problem = Some(problem.map_or(String::new(), |p| p + "; ") + &format!("construct.plane.offset: {e}")),
}
let known = known_without_attachment(&name, &body, normal, distance);
out.push((name.clone(), body, index, known, problem));
}
assert!(s.model.results.iter().all(|r| r.error.is_none()), "{name}: left with errors after undo");
}
out
}
/// Every flat face of every corpus design takes a durable attachment (issue #489: the top face
/// of an extrude sketched over a face's projected boundary had "no exact planar source"),
/// except the faces listed in [`NO_ATTACHMENT_YET`] under their open issues.
#[test]
fn every_flat_face_of_every_corpus_design_takes_an_attached_sketch() {
let all = flat_face_attachments();
assert!(all.len() > 50, "only {} faces checked", all.len());
let problems: Vec<String> = all
.iter()
.filter_map(|(design, body, face, known, problem)| match (known, problem) {
(None, Some(problem)) => Some(format!("{design}: {body} face {face}: {problem}")),
(Some((issue, true)), None) => Some(format!("{design}: {body} face {face} attaches now: remove it from NO_ATTACHMENT_YET (#{issue})")),
_ => None,
})
.collect();
assert!(problems.is_empty(), "{} of {} faces:\n{}", problems.len(), all.len(), problems.join("\n"));
}
fn known_faces_attach(issue: u32) {
let problems: Vec<String> = flat_face_attachments()
.into_iter()
.filter(|(.., known, problem)| known.is_some_and(|(known, _)| known == issue) && problem.is_some())
.map(|(design, body, face, _, problem)| format!("{design}: {body} face {face}: {}", problem.unwrap_or_default()))
.collect();
assert!(problems.is_empty(), "{}", problems.join("\n"));
}
#[test]
#[ignore = "fails until #628 is fixed"]
fn faces_of_a_lid_with_joined_ribs_take_an_attached_sketch() {
known_faces_attach(628);
}
/// A small deterministic generator (xorshift).
struct Rng(u64);
impl Rng {
@@ -0,0 +1,203 @@
//! A parameter change gives the same topology as building at the new value from scratch, and
//! changing it back gives the original topology: no stale splits, no faces added to regions
//! nothing touched. Over the design corpus (`tests/corpus`, `tests/action_matrix/designs`), and
//! the README demo's Raspberry Pi case pinned step by step.
use std::collections::BTreeMap;
use std::path::{Path, PathBuf};
use serde_json::{Value, json};
use solidcraft_engine::Session;
use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
fn root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
}
/// A folder of its own for test `name` (tests run side by side).
fn scratch_dir(name: &str) -> PathBuf {
let d = std::env::temp_dir().join(format!("solidcraft-rebuild-topology-{name}-{}", std::process::id()));
std::fs::create_dir_all(&d).unwrap();
d
}
/// A body's topology: its face and edge counts, and how many faces lie in each plane.
#[derive(Debug, PartialEq, Eq)]
struct Topology {
faces: usize,
edges: usize,
planes: BTreeMap<[i64; 4], usize>,
}
/// Every solid body's topology, by name.
fn topology(s: &Session) -> BTreeMap<String, Topology> {
let st = s.world_state();
st.bodies
.iter()
.filter(|b| !b.body.is_mesh())
.map(|b| {
let mut planes = BTreeMap::new();
for i in 0..b.body.face_count() {
if let Some(FaceSurface::Plane { normal, point }) = face_surface(&b.body, i) {
let key = [normal.x, normal.y, normal.z, normal.dot(point) / 100.].map(|x| (x * 1e4).round() as i64);
*planes.entry(key).or_insert(0) += 1;
}
}
(b.name.clone(), Topology { faces: b.body.face_count(), edges: b.body.edge_count(), planes })
})
.collect()
}
fn open(path: &Path) -> Session {
let mut s = Session::default();
s.execute("doc.open", &json!({"path": path.to_string_lossy()})).unwrap_or_else(|e| panic!("{}: {e}", path.display()));
s
}
/// The design built from scratch: saved as it is (timeline only, no cached geometry) and
/// opened again.
fn rebuilt(s: &Session, path: &Path) -> Session {
let mut t = s.scratch();
t.execute("file.save", &json!({"path": path.to_string_lossy(), "format": "json"})).unwrap();
open(path)
}
fn errors(s: &Session) -> Vec<u64> {
s.model.results.iter().filter(|r| r.error.is_some()).map(|r| r.id).collect()
}
/// The corpus's designs, as sessions.
fn designs() -> Vec<(String, Session)> {
let root = root();
let mut out = Vec::new();
for dir in ["apps/solidcraft-cli/tests/corpus", "tests/action_matrix/designs"] {
let mut files: Vec<PathBuf> = std::fs::read_dir(root.join(dir))
.unwrap()
.flatten()
.map(|e| e.path())
.filter(|p| p.extension().is_some_and(|x| x == "solidcraft" || x == "json") && !p.to_string_lossy().ends_with(".expected.json"))
.collect();
files.sort();
for p in files {
let name = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
let s = if p.extension().is_some_and(|x| x == "solidcraft") {
open(&p)
} else {
let mut s = Session::default();
let script: Value = serde_json::from_slice(&std::fs::read(&p).unwrap()).unwrap();
s.run_script(&script).unwrap_or_else(|e| panic!("{name}: {e}"));
if s.active_sketch.is_some() {
let _ = s.execute("sketch.finish", &json!({}));
}
s
};
out.push((name, s));
}
}
out
}
/// For each design and its first length parameter that edits cleanly (the user's first): the parameter
/// scaled by 1.1 gives what a fresh build at that value gives, and setting it back gives the
/// original topology.
#[test]
fn a_parameter_edit_rebuilds_like_a_fresh_build_and_reverts_exactly() {
let dir = scratch_dir("corpus");
let mut problems = Vec::new();
let mut checked = 0;
for (name, s) in designs() {
let original = topology(&s);
let before = errors(&s);
let mut params: Vec<_> = s.doc.params.iter().filter(|p| p.unit == "mm").cloned().collect();
params.sort_by_key(|p| p.model);
let mut tried = 0;
for p in params {
if tried == 1 {
break;
}
let mut t = s.scratch();
let expr = format!("({}) * 1.1", p.expr);
if t.execute("parameters.change", &json!({"name": p.name, "expression": expr})).is_err() || errors(&t) != before {
continue;
}
tried += 1;
checked += 1;
let edited = topology(&t);
let fresh = topology(&rebuilt(&t, &dir.join(format!("{name}-{}.solidcraft", p.name))));
if edited != fresh {
problems.push(format!("{name}: {} = {expr}: edited {edited:?}\n fresh {fresh:?}", p.name));
}
t.execute("parameters.change", &json!({"name": p.name, "expression": p.expr})).unwrap();
let back = topology(&t);
if back != original {
problems.push(format!("{name}: {} back to {}: {back:?}\n original {original:?}", p.name, p.expr));
}
}
}
let _ = std::fs::remove_dir_all(&dir);
assert!(checked >= 8, "only {checked} parameter edits checked");
assert!(problems.is_empty(), "{}", problems.join("\n"));
}
/// The README demo's Raspberry Pi case (`examples/demo/enclosure_ui_mcp.py --story pi`): its
/// face count after each feature, and each outer side (the four walls and the bottom) one face
/// throughout: (a) built at height 24, (b) built at 24 and changed to 32 in Parameters, (c)
/// built at 32. The port cut-outs and the floor vents used to split the long wall (or the end
/// wall at 24) into strips along the openings' tops and bottoms.
#[test]
fn pi_case_faces_per_step_at_both_heights() {
// Sketch1 (1) .. RectangularPattern2 (14): the faces once features 1..=k are built.
const FACES: [(usize, usize); 13] =
[(2, 6), (3, 10), (4, 19), (5, 19), (6, 25), (7, 43), (8, 43), (9, 55), (10, 55), (11, 59), (12, 59), (13, 63), (14, 79)];
let dir = scratch_dir("pi");
let design: Value = serde_json::from_slice(&std::fs::read(root().join("tests/action_matrix/designs/pi-case.solidcraft")).unwrap()).unwrap();
let at = |height: &str, file: &str| {
let mut d = design.clone();
for p in d["params"].as_array_mut().unwrap() {
if p["name"] == "height" {
p["expr"] = json!(height);
}
}
let path = dir.join(file);
std::fs::write(&path, serde_json::to_vec(&d).unwrap()).unwrap();
open(&path)
};
let a = at("24 mm", "pi-24.solidcraft");
let mut b = at("24 mm", "pi-24-to-32.solidcraft");
b.execute("parameters.change", &json!({"name": "height", "expression": "32 mm"})).unwrap();
let c = at("32 mm", "pi-32.solidcraft");
let _ = std::fs::remove_dir_all(&dir);
let mut per_case = Vec::new();
for (case, s, h) in [("(a) at 24", a, 24.), ("(b) 24 changed to 32", b, 32.), ("(c) at 32", c, 32.)] {
assert!(errors(&s).is_empty(), "{case}: features fail: {:?}", errors(&s));
let mut steps = Vec::new();
for (k, want) in FACES {
let mut t = s.scratch();
t.execute("timeline.rollback", &json!({"position": k})).unwrap();
let st = t.world_state();
let body = st.bodies.iter().find(|b| b.name == "Body1").unwrap();
assert_eq!(body.body.face_count(), want, "{case}: faces after feature {k}");
// Each outer side: the faces in its plane.
for (n, d) in [([0., -1., 0.], 33.), ([0., 1., 0.], 33.), ([-1., 0., 0.], 47.5), ([1., 0., 0.], 47.5), ([0., 0., -1.], 0.)] {
let on = (0..body.body.face_count())
.filter(|&i| match face_surface(&body.body, i) {
Some(FaceSurface::Plane { normal, point }) => {
(normal.x - n[0]).abs() + (normal.y - n[1]).abs() + (normal.z - n[2]).abs() < 1e-6 && (normal.dot(point) - d).abs() < 1e-6
}
_ => false,
})
.count();
assert_eq!(on, 1, "{case}: after feature {k} the outer side facing {n:?} is {on} faces");
}
let bbox_top = body.mesh().bounds().max.z;
assert!((bbox_top - h).abs() < 1e-6, "{case}: height {bbox_top}");
steps.push(topology(&t));
}
per_case.push((case, steps));
}
// (b) and (c): the same topology after every feature.
let (b, c) = (&per_case[1].1, &per_case[2].1);
for (k, (x, y)) in b.iter().zip(c).enumerate() {
assert_eq!(x, y, "after feature {}: (b) differs from (c)", k + 2);
}
}
+6
View File
@@ -8,3 +8,9 @@ filehash = true
[serve]
addresses = ["127.0.0.1"]
port = 8781
# Offline use and updates: release builds get sw.js listing this build's files (xtask/src/web_offline.rs).
[[hooks]]
stage = "post_build"
command = "cargo"
command_arguments = ["xtask", "web-offline"]
+24 -8
View File
@@ -4,18 +4,34 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>SolidCraft</title>
<meta name="description" content="Open-source parametric CAD built in Rust. Desktop, browser, and MCP for AI agents.">
<meta property="og:type" content="website">
<meta property="og:site_name" content="SolidCraft">
<meta property="og:title" content="SolidCraft: try it in your browser">
<meta property="og:description" content="Open-source parametric CAD built in Rust. Desktop, browser, and MCP for AI agents.">
<meta property="og:url" content="https://bherbruck.github.io/solidcraft/app/">
<meta property="og:image" content="https://bherbruck.github.io/solidcraft/og.png">
<meta property="og:image:width" content="1280">
<meta property="og:image:height" content="640">
<meta property="og:image:alt" content="SolidCraft: open-source parametric CAD built in Rust">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="SolidCraft: try it in your browser">
<meta name="twitter:description" content="Open-source parametric CAD built in Rust. Desktop, browser, and MCP for AI agents.">
<meta name="twitter:image" content="https://bherbruck.github.io/solidcraft/og.png">
<meta name="twitter:image:alt" content="SolidCraft: open-source parametric CAD built in Rust">
<link data-trunk rel="rust" data-bin="solidcraft-web" data-wasm-opt="z" data-weak-refs />
<link data-trunk rel="copy-file" href="fonts/BIZUDPGothic-Regular.ttf" data-target-path="fonts" />
<link data-trunk rel="copy-file" href="fonts/OFL.txt" data-target-path="fonts" />
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #e8ebf0; }
#solidcraft_canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#solidcraft_loading { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
color: #4a5060; font: 14px system-ui, sans-serif; pointer-events: none; }
</style>
<link data-trunk rel="copy-file" href="../../assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf" data-target-path="fonts" />
<link data-trunk rel="copy-file" href="../../assets/fonts/biz-udp-gothic/OFL.txt" data-target-path="fonts" />
<link data-trunk rel="css" href="../../assets/styles/web/loading.css" />
</head>
<body>
<canvas id="solidcraft_canvas"></canvas>
<div id="solidcraft_loading">Loading SolidCraft&hellip;</div>
<script>
// Offline use and updates (sw.js; written into release builds by `cargo xtask web-offline`).
if ("serviceWorker" in navigator) {
addEventListener("load", () => navigator.serviceWorker.register("sw.js").catch(() => {}));
}
</script>
</body>
</html>
+26 -10
View File
@@ -110,7 +110,7 @@ mod web {
let changed = s.is_dirty() && self.autosaved != Some(s.revision);
let wait = AUTOSAVE_S - (now - self.autosaved_at);
if changed && wait <= 0.0 {
files::autosave(&self.files, Some((&s.doc, s.path.as_deref())));
files::autosave(&self.files, Some((&s.design(), s.path.as_deref())));
self.autosaved = Some(s.revision);
self.autosaved_at = now;
} else if changed {
@@ -251,7 +251,10 @@ mod web {
},
"download_open" => {
let d = &self.app.session.doc;
let result = solidcraft_engine::io::write_design_container(d)
let result = self
.app
.session
.design_container(solidcraft_engine::io::DesignCompression::Fast, false, false)
.map_err(|e| e.to_string())
.and_then(|bytes| files::download(&format!("{}.{}", d.name, solidcraft_engine::io::DESIGN_EXT), &bytes));
if let Err(e) = result {
@@ -328,22 +331,34 @@ mod web {
};
if installed {
log::info!("loaded web CJK font from {JAPANESE_FONT_PATH}");
} else {
} else if solidcraft_ui_egui::fonts::needs_cjk_font(&self.app.preferences.language) {
log::warn!("CJK font unavailable; showing English for this session");
}
self.font.finished(installed, &mut self.app.preferences.language);
ctx.request_repaint();
}
if self.font.tick(&mut self.app.preferences.language) {
let slot = self.font_result.clone();
let ctx = ctx.clone();
wasm_bindgen_futures::spawn_local(async move {
let res = fetch_font_bytes(JAPANESE_FONT_PATH).await;
*slot.borrow_mut() = Some(res);
ctx.request_repaint();
});
self.fetch_font(ctx);
}
}
/// CJK text on screen with no CJK font yet (an English UI showing a Japanese name):
/// fetch the font, so the text does not stay tofu (#720).
fn watch_text_for_font(&mut self, ctx: &egui::Context) {
if self.font.watching_text() && solidcraft_ui_egui::fonts::painted_cjk(ctx) && self.font.text_needs_font() {
self.fetch_font(ctx);
}
}
fn fetch_font(&self, ctx: &egui::Context) {
let slot = self.font_result.clone();
let ctx = ctx.clone();
wasm_bindgen_futures::spawn_local(async move {
let res = fetch_font_bytes(JAPANESE_FONT_PATH).await;
*slot.borrow_mut() = Some(res);
ctx.request_repaint();
});
}
}
impl eframe::App for Shell {
@@ -362,6 +377,7 @@ mod web {
let ctx = ui.ctx().clone();
self.file_panel(&ctx);
self.recovery_prompt(&ctx);
self.watch_text_for_font(&ctx);
}
fn save(&mut self, storage: &mut dyn eframe::Storage) {
// Persist the user's own language, not a session-only English fallback.
+227
View File
@@ -0,0 +1,227 @@
// SolidCraft's service worker: the web app keeps working offline and updates itself.
//
// `cargo xtask web-offline` (Trunk's post_build hook) writes the build into the line below: the
// version (workspace version + a hash of every built file) and the files to keep, each with its
// SHA-256, and the same as `build.json` next to it. Files the app only fetches when it needs
// them (the CJK font) aren't kept: they come from the network. Every download is checked
// against its SHA-256: a deployment that lands mid-download (another build's `index.html`) fails
// the download instead of mixing into the kept build. A new build changes this file, so the
// browser installs the new worker, which keeps the whole new build before taking over.
//
// A launch only ever starts a build whose every file is kept, so a half-downloaded update can't
// break it. It asks for `build.json` (waiting at most NAVIGATE_TIMEOUT_MS): offline, slow or
// unchanged, the kept build starts; when a new build is deployed, it is downloaded first
// (waiting at most UPDATE_TIMEOUT_MS; the download goes on in the background after that) and
// started. When the site has no `build.json` (the app was removed), launches go to the network,
// the kept build answering when that fails. Each page then gets its files from the build it
// started (remembered in PAGES), never from another build with a file of the same name.
//
// A cache is only deleted when nothing uses it: a download holds its lock (Web Locks, shared,
// in any tab or worker; the browser releases it when the download ends or its worker stops), and
// an open page uses the build it started. Then unfinished downloads are dropped, and so are
// builds replaced by a newer worker (RETIRED). The origin is shared with other apps (github.io)
// and other copies of this one (the release zip works from any path): only caches named after
// this worker's path (CACHE_PREFIX…, PAGES) are ever touched.
//
// Unbuilt (VERSION "dev", as debug builds get it), the worker retires: it drops what a release
// build kept and unregisters, so `trunk serve` after `trunk serve --release --no-autoreload`
// shows the development build. Deploying this file unbuilt switches the worker off for everyone.
/* @build */ const VERSION = "dev", FILES = [];
const RETIRE = VERSION === "dev";
const PATH = new URL(self.registration.scope).pathname;
const CACHE_PREFIX = `solidcraft-app ${PATH} `;
const CACHE = CACHE_PREFIX + VERSION;
/** Which build each open page started: page id → cache name (or NETWORK). */
const PAGES = `solidcraft-app-pages ${PATH}`;
/** A page that came from the network gets its files from there too. */
const NETWORK = "network";
const NAVIGATE_TIMEOUT_MS = 4000;
const UPDATE_TIMEOUT_MS = 20000;
/** The entry that marks a cache as holding its whole build. */
const COMPLETE = "build-complete";
/** The entry that marks a build replaced by a newer worker. */
const RETIRED = "build-retired";
/** A page remembered this recently may still be loading (not yet listed among the clients). */
const PAGE_LOADING_MS = 30 * 1000;
const url = (file) => new URL(file, self.registration.scope).href;
const has = (name, entry) => caches.match(url(entry), { cacheName: name }).then(Boolean);
/** Run `fn` while using the cache `name`: it isn't deleted meanwhile. */
const using = (name, fn) => navigator.locks.request(name, { mode: "shared" }, fn);
/** Delete the cache `name` if nothing is using it and `unwanted()` (asked once no one can start to) agrees. */
const drop = (name, unwanted) =>
navigator.locks.request(name, { ifAvailable: true }, async (free) => free && (await unwanted()) && caches.delete(name));
const hex = (buffer) => Array.from(new Uint8Array(buffer), (b) => b.toString(16).padStart(2, "0")).join("");
/**
* Keep every file of a build (an earlier partial download is resumed); resolves to its cache's
* name. The first file that fails cancels the others.
*/
function keep(version, files) {
const name = CACHE_PREFIX + version;
return using(name, async () => {
const cache = await caches.open(name);
await cache.delete(url(RETIRED)); // Back in use (a rollback).
if (await has(name, COMPLETE)) return name;
const cancel = new AbortController();
const results = await Promise.allSettled(
files.map(async ({ file, sha256 }) => {
try {
if (await cache.match(url(file))) return;
// `no-cache`: revalidate with the server, so a stale HTTP-cached page never mixes builds.
const response = await fetch(new Request(url(file), { cache: "no-cache" }), { signal: cancel.signal });
if (!response.ok) throw new Error(`${file}: HTTP ${response.status}`);
const body = await response.arrayBuffer();
if (hex(await crypto.subtle.digest("SHA-256", body)) !== sha256) throw new Error(`${file}: not this build's`);
await cache.put(url(file), new Response(body, { headers: response.headers }));
} catch (error) {
cancel.abort();
throw error;
}
}),
);
const failed = results.find((r) => r.status === "rejected");
if (failed) throw failed.reason;
await cache.put(url(COMPLETE), new Response(version));
return name;
});
}
const pageEntry = (id) => url(`~page/${encodeURIComponent(id)}`);
/** Remember which build a page started. */
async function started(id, build) {
if (!id) return;
const record = JSON.stringify({ build, at: Date.now() });
await (await caches.open(PAGES)).put(pageEntry(id), new Response(record));
}
/** The build a page started; a page started before it was remembered gets this worker's. */
async function startedBy(id) {
const response = id ? await caches.match(pageEntry(id), { cacheName: PAGES }) : null;
return response ? (await response.json()).build : CACHE;
}
/** The builds open pages use (pages closed since are forgotten). */
async function inUse() {
const open = new Set((await self.clients.matchAll({ includeUncontrolled: true })).map((c) => c.id));
const pages = await caches.open(PAGES);
const builds = new Set();
for (const request of await pages.keys()) {
const id = decodeURIComponent(request.url.split("~page/")[1] ?? "");
const record = await pages.match(request).then((r) => r?.json()).catch(() => null);
if (record && (open.has(id) || Date.now() - record.at < PAGE_LOADING_MS)) builds.add(record.build);
else await pages.delete(request);
}
return builds;
}
/** Drop unfinished downloads nothing is using, and replaced builds no open page uses. */
async function sweep() {
for (const name of await caches.keys()) {
if (!name.startsWith(CACHE_PREFIX) || name === CACHE) continue;
await drop(name, async () => !(await has(name, COMPLETE)) || ((await has(name, RETIRED)) && !(await inUse()).has(name)));
}
}
self.addEventListener("install", (event) => {
// Clean up first: a page open for days sees updates (and failed ones) without any launch.
const install = () => sweep().catch(() => {}).then(() => keep(VERSION, FILES));
event.waitUntil(RETIRE ? self.skipWaiting() : install().then(() => self.skipWaiting()));
});
self.addEventListener("activate", (event) => {
event.waitUntil(
(async () => {
if (RETIRE) {
for (const name of await caches.keys()) if (name.startsWith(CACHE_PREFIX) || name === PAGES) await caches.delete(name);
return self.registration.unregister();
}
for (const name of await caches.keys()) {
if (name.startsWith(CACHE_PREFIX) && name !== CACHE) await (await caches.open(name)).put(url(RETIRED), new Response(""));
}
await sweep();
await self.clients.claim();
})(),
);
});
/** The promise's value, or null when it fails or takes longer than `ms`. */
function within(ms, promise) {
return Promise.race([promise.catch(() => null), new Promise((resolve) => setTimeout(resolve, ms, null))]);
}
/** No app is deployed here any more (the server says `build.json` doesn't exist). */
const GONE = "gone";
/** The deployed build's version and files, GONE, or null (unknown: offline, failing, invalid). */
async function deployed() {
const response = await fetch(url("build.json"), { cache: "no-cache" });
if (response.status === 404 || response.status === 410) return GONE;
const build = response.ok ? await response.json() : null;
const valid = build && typeof build.version === "string" && Array.isArray(build.files);
return valid && build.files.every((f) => typeof f.file === "string" && typeof f.sha256 === "string") ? build : null;
}
/** Keep the worker alive for `promise` (allowed while the launch is being answered). */
function extend(event, promise) {
try {
event.waitUntil(promise.catch(() => {}));
} catch {
// The event has ended; the work goes on while the worker runs.
}
}
/** A launch: the start page of a complete build (whatever query the URL has: `?sample`), and that build. */
async function launch(event) {
const kept = await caches.match(url("index.html"), { cacheName: CACHE });
if (!kept) return [await fetch(event.request), NETWORK]; // Nothing kept yet (the first visit).
extend(event, sweep());
const build = await within(NAVIGATE_TIMEOUT_MS, deployed());
if (build === GONE) {
const page = await fetch(event.request).catch(() => null);
return page && page.ok ? [page, NETWORK] : [kept, CACHE];
}
if (!build || build.version === VERSION) return [kept, CACHE];
const download = keep(build.version, build.files);
extend(event, download);
const name = await within(UPDATE_TIMEOUT_MS, download);
const page = name && (await caches.match(url("index.html"), { cacheName: name }));
return page ? [page, name] : [kept, CACHE];
}
/**
* A launch that fails unexpectedly still starts the kept build (or asks the network). A newer
* worker only takes over once the launch has ended, by when its page is remembered (in use).
*/
async function launchOrKept(event) {
let page, build;
try {
[page, build] = await launch(event);
} catch {
page = await caches.match(url("index.html"), { cacheName: CACHE }).catch(() => null);
build = page ? CACHE : NETWORK;
page = page || (await fetch(event.request));
}
await started(event.resultingClientId, build).catch(() => {});
return page;
}
/** A page's files come from the build it started, else the network. */
async function asset(event) {
const build = await startedBy(event.clientId).catch(() => CACHE);
const kept = build === NETWORK ? null : await caches.match(event.request, { cacheName: build, ignoreSearch: true });
return kept || fetch(event.request);
}
self.addEventListener("fetch", (event) => {
const request = event.request;
if (RETIRE || request.method !== "GET" || new URL(request.url).origin !== self.location.origin) return;
const path = request.url.split(/[?#]/)[0];
const start = request.mode === "navigate" && (path === url("") || path === url("index.html"));
event.respondWith(start ? launchOrKept(event) : asset(event));
});
+1
View File
@@ -18,6 +18,7 @@ egui = { workspace = true }
serde_json = { workspace = true }
log = { workspace = true }
rfd = { workspace = true }
getrandom = "0.3"
[target.'cfg(any(target_os = "windows", target_os = "linux"))'.dependencies]
quick-xml = { workspace = true }
+5 -49
View File
@@ -1,61 +1,17 @@
#![forbid(unsafe_code)]
#[path = "../app_icon.rs"]
mod app_icon;
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=../app_icon.rs");
let icon = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../assets/icons/app/windows/exports/solidcraft-executable.ico");
println!("cargo:rerun-if-changed={}", icon.display());
println!("cargo:rerun-if-changed=build.rs");
if std::env::var("CARGO_CFG_TARGET_OS")? != "windows" {
return Ok(());
}
let out = std::path::PathBuf::from(std::env::var_os("OUT_DIR").ok_or("missing OUT_DIR")?);
let sizes = [16usize, 24, 32, 48, 64, 128, 256];
let mut images = Vec::new();
for size in sizes {
let rgba = app_icon::rgba(size)?;
// ICO DIB: bottom-up BGRA plus a padded one-bit transparency mask.
let mask_stride = size.div_ceil(32) * 4;
let mut dib = Vec::new();
dib.extend_from_slice(&40u32.to_le_bytes());
dib.extend_from_slice(&(size as i32).to_le_bytes());
dib.extend_from_slice(&((size * 2) as i32).to_le_bytes());
dib.extend_from_slice(&1u16.to_le_bytes());
dib.extend_from_slice(&32u16.to_le_bytes());
dib.extend_from_slice(&[0; 24]);
for row in rgba.chunks_exact(size * 4).rev() {
for p in row.as_chunks::<4>().0.iter() {
dib.extend_from_slice(&[p[2], p[1], p[0], p[3]]);
}
}
for row in rgba.chunks_exact(size * 4).rev() {
let mut mask = vec![0u8; mask_stride];
for (x, p) in row.as_chunks::<4>().0.iter().enumerate() {
if p[3] == 0 {
mask[x / 8] |= 0x80 >> (x % 8);
}
}
dib.extend_from_slice(&mask);
}
images.push((size, dib));
}
let mut ico = vec![0, 0, 1, 0];
ico.extend_from_slice(&(images.len() as u16).to_le_bytes());
let mut offset = 6 + 16 * images.len();
for (size, data) in &images {
let dim = if *size == 256 { 0 } else { *size as u8 };
ico.extend_from_slice(&[dim, dim, 0, 0, 1, 0, 32, 0]);
ico.extend_from_slice(&(data.len() as u32).to_le_bytes());
ico.extend_from_slice(&(offset as u32).to_le_bytes());
offset += data.len();
}
for (_, data) in images {
ico.extend_from_slice(&data);
}
let icon = out.join("solidcraft.ico");
std::fs::write(&icon, ico)?;
let copy = out.join("solidcraft.ico");
std::fs::copy(icon, &copy)?;
let rc = out.join("solidcraft.rc");
std::fs::write(&rc, format!("1 ICON \"{}\"\n", icon.display().to_string().replace('\\', "/")))?;
// Missing resource tools are a build error, rather than silently shipping a generic icon.
std::fs::write(&rc, format!("1 ICON \"{}\"\n", copy.display().to_string().replace('\\', "/")))?;
embed_resource::compile_for(&rc, ["solidcraft"], embed_resource::NONE).manifest_required()?;
Ok(())
}
+133 -6
View File
@@ -9,18 +9,39 @@ use std::time::Duration;
use serde_json::{Value, json};
use solidcraft_engine::MAX_LINE;
use solidcraft_engine::control_auth;
use solidcraft_ui_egui::ControlRequest;
/// Connections served at once. The channel has no authentication beyond being bound to
/// loopback, so a flood must not be able to spawn threads without end.
/// Connections served at once, so a flood must not be able to spawn threads without end.
const MAX_CONNECTIONS: usize = 16;
/// Lines that are not JSON before the connection is closed, so garbage cannot loop forever.
const MAX_BAD_LINES: usize = 8;
static OPEN: AtomicUsize = AtomicUsize::new(0);
/// The token file this launch wrote, removed when the app ends.
static TOKEN_FILE: std::sync::OnceLock<std::path::PathBuf> = std::sync::OnceLock::new();
/// A new random token: 32 bytes from the operating system, as hex.
fn new_token() -> Option<String> {
let mut bytes = [0u8; 32];
getrandom::fill(&mut bytes).ok()?;
Some(bytes.iter().map(|b| format!("{b:02x}")).collect())
}
/// Remove this launch's token file (at exit).
pub fn remove_token_file() {
if let Some(p) = TOKEN_FILE.get() {
let _ = std::fs::remove_file(p);
}
}
pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
let (tx, rx) = channel::<ControlRequest>();
// Every connection must show the token before anything runs (docs/control-protocol.md).
let Some(token) = control_auth::env_token().or_else(new_token) else {
eprintln!("solidcraft: control server not started: no random token available");
return rx;
};
let listener = match TcpListener::bind(("127.0.0.1", port)) {
Ok(l) => l,
Err(e) => {
@@ -28,7 +49,23 @@ pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
return rx;
}
};
eprintln!("solidcraft: control server listening on 127.0.0.1:{port}");
match control_auth::token_path(port) {
Some(path) => match control_auth::write_token_file(&path, &token) {
Ok(()) => {
eprintln!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
let _ = TOKEN_FILE.set(path);
}
Err(e) => eprintln!(
"solidcraft: control server listening on 127.0.0.1:{port}, but its token file {} could not be written ({e}); \
clients need SOLIDCRAFT_CONTROL_TOKEN",
path.display()
),
},
None => {
eprintln!("solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN")
}
}
let token = std::sync::Arc::new(token);
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
if OPEN.fetch_add(1, Ordering::Relaxed) >= MAX_CONNECTIONS {
@@ -39,8 +76,9 @@ pub fn start(port: u16, ctx: egui::Context) -> Receiver<ControlRequest> {
}
let tx = tx.clone();
let ctx = ctx.clone();
let token = token.clone();
std::thread::spawn(move || {
serve(stream, tx, ctx);
serve(stream, &token, tx, ctx);
OPEN.fetch_sub(1, Ordering::Relaxed);
});
}
@@ -80,11 +118,28 @@ fn read_line(r: &mut impl BufRead, max: usize) -> std::io::Result<Option<Vec<u8>
}
}
fn serve(stream: TcpStream, tx: Sender<ControlRequest>, ctx: egui::Context) {
/// The reply to a connection that didn't show the token; the connection is then closed.
fn unauthorized() -> Value {
json!({"ok": false, "error": "unauthorized: send the control token as \"token\" (see docs/control-protocol.md)"})
}
/// Refuse the connection: the reply, then the end of what we send, then what the client had
/// already sent read and dropped (a little, briefly). Closing a socket with unread input resets
/// the connection, and a reset can reach the client before it has read the refusal.
fn refuse(out: &mut TcpStream, reader: &mut BufReader<TcpStream>) {
use std::io::Read;
let _ = writeln!(out, "{}", unauthorized());
let _ = out.shutdown(std::net::Shutdown::Write);
let _ = reader.get_ref().set_read_timeout(Some(Duration::from_millis(500)));
let _ = std::io::copy(&mut reader.by_ref().take(64 * 1024), &mut std::io::sink());
}
fn serve(stream: TcpStream, token: &str, tx: Sender<ControlRequest>, ctx: egui::Context) {
let Ok(read) = stream.try_clone() else { return };
let mut out = stream;
let mut reader = BufReader::new(read);
let mut bad = 0usize;
let mut authenticated = false;
loop {
let line = match read_line(&mut reader, MAX_LINE) {
Ok(Some(l)) => String::from_utf8_lossy(&l).into_owned(),
@@ -99,20 +154,36 @@ fn serve(stream: TcpStream, tx: Sender<ControlRequest>, ctx: egui::Context) {
}
let reply = match serde_json::from_str::<Value>(&line) {
Ok(msg) => {
// The first request must carry the token; later ones on the same connection may.
if !authenticated {
match msg.get("token").and_then(Value::as_str) {
Some(t) if control_auth::tokens_match(t, token) => authenticated = true,
_ => {
refuse(&mut out, &mut reader);
break;
}
}
}
let id = msg.get("id").cloned().unwrap_or(Value::Null);
let method = msg.get("method").and_then(Value::as_str).unwrap_or("").to_string();
let params = msg.get("params").cloned().unwrap_or(json!({}));
// A batch replaces many short requests: the whole of it may take long (#743).
let wait = Duration::from_secs(if method == "engine.batch" { 3600 } else { 60 });
let (req, rrx) = ControlRequest::new(method, params);
if tx.send(req).is_err() {
break;
}
ctx.request_repaint();
let mut r = rrx.recv_timeout(Duration::from_secs(60)).unwrap_or_else(|_| json!({"ok": false, "error": "timeout"}));
let mut r = rrx.recv_timeout(wait).unwrap_or_else(|_| json!({"ok": false, "error": "timeout"}));
if let Some(o) = r.as_object_mut() {
o.insert("id".into(), id);
}
r
}
Err(_) if !authenticated => {
refuse(&mut out, &mut reader);
break;
}
Err(e) => {
bad += 1;
json!({"ok": false, "error": format!("bad JSON: {e}")})
@@ -131,6 +202,62 @@ fn serve(stream: TcpStream, tx: Sender<ControlRequest>, ctx: egui::Context) {
mod tests {
use super::*;
/// #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.
#[test]
fn requests_run_only_after_the_token() {
use std::io::Read;
let token = "0123456789abcdef0123456789abcdef";
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
let addr = listener.local_addr().unwrap();
let (tx, rx) = channel::<ControlRequest>();
let ran = std::sync::Arc::new(std::sync::Mutex::new(Vec::<String>::new()));
let seen = ran.clone();
std::thread::spawn(move || {
for req in rx {
seen.lock().unwrap().push(req.method.clone());
let _ = req.reply.send(json!({"ok": true, "result": null}));
}
});
std::thread::spawn(move || {
for stream in listener.incoming().flatten() {
let tx = tx.clone();
std::thread::spawn(move || serve(stream, token, tx, egui::Context::default()));
}
});
let session = |lines: &[String]| {
let mut s = TcpStream::connect(addr).unwrap();
s.set_read_timeout(Some(Duration::from_secs(10))).unwrap();
for l in lines {
let _ = writeln!(s, "{l}");
}
let _ = s.shutdown(std::net::Shutdown::Write);
let mut text = String::new();
let _ = s.read_to_string(&mut text);
text.lines().map(|l| serde_json::from_str::<Value>(l).unwrap()).collect::<Vec<_>>()
};
for first in [
json!({"id": 1, "method": "app.quit", "params": {"force": true}}).to_string(),
json!({"id": 1, "method": "app.quit", "token": "0123456789abcdef0123456789abcdee"}).to_string(),
json!({"id": 1, "method": "app.quit", "token": "0123"}).to_string(),
json!({"id": 1, "method": "app.quit", "token": 7}).to_string(),
"not json".to_string(),
] {
let replies = session(&[first.clone(), json!({"id": 2, "method": "app.quit", "token": token}).to_string()]);
assert_eq!(replies.len(), 1, "{first}: closed after refusing: {replies:?}");
assert!(replies[0]["error"].as_str().is_some_and(|e| e.starts_with("unauthorized")), "{first}: {replies:?}");
}
assert!(ran.lock().unwrap().is_empty(), "nothing ran without the token");
let replies = session(&[
json!({"id": 1, "method": "ui.inspect", "token": token}).to_string(),
json!({"id": 2, "method": "engine.commands"}).to_string(),
]);
assert_eq!(replies.len(), 2, "{replies:?}");
assert!(replies.iter().all(|r| r["ok"] == true), "{replies:?}");
assert_eq!(*ran.lock().unwrap(), ["ui.inspect", "engine.commands"]);
}
/// A request longer than the cap is refused instead of allocated.
#[test]
fn lines_past_the_cap_are_refused() {
+24 -12
View File
@@ -13,7 +13,7 @@
#![forbid(unsafe_code)]
#[cfg(not(target_os = "macos"))]
#[path = "../../app_icon.rs"]
#[path = "../../../assets/icons/app/shared/source/app_icon.rs"]
mod app_icon;
mod control_server;
#[cfg(any(target_os = "windows", target_os = "linux"))]
@@ -141,17 +141,28 @@ fn graphics_path() -> Option<std::path::PathBuf> {
/// Compatibility shim (docs/compatibility.md): on the first launch after the rename, copy the
/// settings of earlier releases into the new folder. They kept `graphics.json` (and, on Linux
/// and macOS, the recovery folder) under the old product name, and eframe's `app.ron` under the
/// window's app id. The old folders are never changed.
/// window's old app id. In the Flatpak sandbox, releases before 0.8 kept everything under the
/// 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 legacy_eframe = eframe::storage_dir(APP_ID);
if let Err(e) = adopt_settings(&current, &legacy, legacy_eframe.as_deref()) {
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()) {
log::warn!("settings: could not copy the previous release's settings into {}: {e}", current.display());
}
}
fn adopt_settings(current: &std::path::Path, legacy: &std::path::Path, legacy_eframe: Option<&std::path::Path>) -> std::io::Result<()> {
fn adopt_settings(
current: &std::path::Path,
old_flatpak: Option<&std::path::Path>,
legacy: &std::path::Path,
legacy_eframe: Option<&std::path::Path>,
) -> std::io::Result<()> {
let fresh = !current.exists();
if let Some(old) = old_flatpak {
solidcraft_engine::io::adopt_legacy_dir(current, old)?;
}
solidcraft_engine::io::adopt_legacy_dir(current, legacy)?;
if let Some(old) = legacy_eframe.map(|d| d.join("app.ron")).filter(|p| fresh && p.is_file()) {
std::fs::create_dir_all(current)?;
@@ -203,9 +214,8 @@ fn app_viewport() -> Result<egui::ViewportBuilder, eframe::Error> {
.with_app_id(APP_ID))
}
/// The window's app id: it names the desktop entry, so it stays the packages' reverse-DNS id,
/// which is kept from before the rename (compatibility shim, docs/compatibility.md).
const APP_ID: &str = "ai.storyteller.solvecraft";
/// The window's app id: it names the desktop entry, so it is the packages' reverse-DNS id.
const APP_ID: &str = solidcraft_engine::io::APP_ID;
fn main() -> eframe::Result {
adopt_legacy_settings();
@@ -276,7 +286,7 @@ fn main() -> eframe::Result {
b.with_x11();
}));
}
eframe::run_native(
let result = eframe::run_native(
"SolidCraft",
options,
Box::new(move |cc| {
@@ -334,7 +344,9 @@ fn main() -> eframe::Result {
space_mouse,
}))
}),
)
);
control_server::remove_token_file();
result
}
/// Whether to run through XWayland rather than native Wayland. winit has no file drag-and-drop
@@ -408,14 +420,14 @@ mod msaa_tests {
std::fs::create_dir_all(&legacy_eframe).unwrap();
std::fs::write(legacy.join("graphics.json"), r#"{"msaa":8}"#).unwrap();
std::fs::write(legacy_eframe.join("app.ron"), "(prefs)").unwrap();
super::adopt_settings(&current, &legacy, Some(&legacy_eframe)).unwrap();
super::adopt_settings(&current, None, &legacy, Some(&legacy_eframe)).unwrap();
assert_eq!(std::fs::read_to_string(current.join("graphics.json")).unwrap(), r#"{"msaa":8}"#);
assert_eq!(std::fs::read_to_string(current.join("app.ron")).unwrap(), "(prefs)");
assert!(legacy.join("graphics.json").is_file() && legacy_eframe.join("app.ron").is_file());
// Later launches leave the current settings alone.
std::fs::write(current.join("app.ron"), "(mine)").unwrap();
std::fs::remove_file(current.join("graphics.json")).unwrap();
super::adopt_settings(&current, &legacy, Some(&legacy_eframe)).unwrap();
super::adopt_settings(&current, None, &legacy, Some(&legacy_eframe)).unwrap();
assert_eq!(std::fs::read_to_string(current.join("app.ron")).unwrap(), "(mine)");
assert!(!current.join("graphics.json").exists());
let _ = std::fs::remove_dir_all(&d);
+92 -35
View File
@@ -1,41 +1,98 @@
# SolidCraft macOS icon
# Application resources
`SolidCraft.icon/` is the approved Icon Composer source, containing separate
grid and solid SVG layers and the owner's appearance settings. The saturated
blue solid has transparent geometry cutouts instead of painted edge lines.
The solid and grid are both scaled to 90%, with separate Default and Dark grid artwork.
Icon Composer supplies the system backdrop and Mono material effects.
Artwork is original and attributed in `ATTRIBUTION.md`.
`assets/` is the design resource home for SolidCraft.
[ATTRIBUTION.md](../ATTRIBUTION.md) is the file inventory and provenance record.
Keep one editable master per resource and place shipped derivatives in `exports/`.
`Assets.car` is compiled by Apple's `actool` and supplies the layered icon on
macOS 26 and later. It is checked in so the existing release workflow can still
package on older Xcode runners. After editing and saving `SolidCraft.icon`,
regenerate the catalog with Xcode 26 or later from the repository root:
```sh
mkdir -p target/macos-icon
xcrun actool assets/SolidCraft.icon \
--compile target/macos-icon --platform macosx \
--minimum-deployment-target 11.0 --app-icon SolidCraft \
--output-partial-info-plist target/macos-icon/icon-info.plist \
--output-format human-readable-text
cp target/macos-icon/Assets.car assets/Assets.car
cp target/macos-icon/SolidCraft.icns assets/icon.icns
```text
assets/
icons/
app/
shared/source/ # original Rust mark and executable ICO encoder
macos/source/SolidCraft.icon/ # approved Composer project and linked SVGs
macos/exports/ # paired Assets.car and icon.icns
windows/exports/ # executable DIB ICO and installer PNG ICO
linux/exports/hicolor/ # Linux/FreeBSD package images, 16–512 px
ui/source/ # original drawing library and other UI glyphs
branding/web/source/og.png # shared social preview card
styles/ui/palettes.rs # light/dark defaults
styles/web/loading.css # web loading/canvas styles
appearances/library.rs # built-in visual presets
materials/library.rs # physical defaults and colours
shaders/viewport.wgsl # original viewport shader
fonts/{liberation-sans,biz-udp-gothic}/ # unmodified fonts and OFL licences
locales/{en,de,ja,ko}.json # shared offline catalogs
platforms/{macos,linux,windows}/ # bundle, freedesktop and WiX templates
archive/icons/macos-v1/
source/icon.vectorcraft # earlier editable work, retained
exports/ # all historical SVG, PNG and iconset exports
```
`icon.icns` is the older-macOS fallback generated by the same Apple compiler
from the approved composition, retaining its layer sizes and positions.
Regenerate both compiled resources together with the commands above.
`icon.vectorcraft`, `icon.svg`, `icon.png` and `icon.iconset/` retain the earlier
VectorCraft artwork; packaging uses the Icon Composer compilation instead.
## Editing and regeneration
Only `packaging/macos/package.sh` consumes these resources. Its existing signed
app/DMG workflow copies the catalog and ICNS into `Contents/Resources`, and
`Info.plist` references `SolidCraft` through `CFBundleIconName` and
`CFBundleIconFile`. The macOS viewport opts out of eframe's runtime bitmap so
Finder, the Dock and the application switcher use the bundle icon. Other
platforms retain the existing code-drawn icon and packaging.
[UI artwork](icons/ui/README.md) identifies the drawing masters and their consumers.
Edit geometry and IDs together in the original Rust source and review changes in
both application themes. Palettes and model presets are also editable Rust data.
Design references: [Apple app-icon HIG](https://developer.apple.com/design/human-interface-guidelines/app-icons)
and [Icon Composer documentation](https://developer.apple.com/documentation/xcode/creating-your-app-icon-using-icon-composer).
No Apple artwork is shipped.
The current macOS master is `icons/app/macos/source/SolidCraft.icon/`, including
its linked SVG layers and appearance settings. The earlier VectorCraft document
is preserved in the archive. No other native design projects were available for
the current UI set or preview card. Keep any future `.vectorcraft`, `.ai` or other
native work and linked inputs under that resource's `source/work/`, with an
attribution row; retain the editable document alongside its exports.
Regenerate all exports of the shared Windows/Linux/FreeBSD mark together:
```sh
cargo xtask app-icons
```
For macOS, use Xcode with Icon Composer support:
```sh
mkdir -p target/resources/macos-icon
xcrun actool assets/icons/app/macos/source/SolidCraft.icon \
--compile target/resources/macos-icon --platform macosx \
--minimum-deployment-target 11.0 --app-icon SolidCraft \
--output-partial-info-plist target/resources/macos-icon/icon-info.plist \
--output-format human-readable-text
cp target/resources/macos-icon/Assets.car assets/icons/app/macos/exports/Assets.car
cp target/resources/macos-icon/SolidCraft.icns assets/icons/app/macos/exports/icon.icns
```
Review and commit source changes and required exports together. Temporary renders,
tool caches and package staging belong under ignored `target/` or `dist/`.
Normal builds use the committed exports and require no design applications.
The CLI still supports arbitrary PNG/ICO exports with `solidcraft-cli icon`.
## Platform bindings
| Platform | Resources and output paths |
| --- | --- |
| macOS | Composer master and paired exports; bundle installs `Assets.car` and `SolidCraft.icns` |
| Windows executable | `solidcraft-executable.ico`: original DIB/mask representations at 16/24/32/48/64/128/256 px |
| Windows installer | `solidcraft.ico`: PNG representations at 16/20/24/32/40/48/64/128/256 px; `SOLIDCRAFT_ICONS` override retained |
| Linux, Nix, deb/rpm, tar, Flatpak, Snap | Copy canonical hicolor images; icon name stays `ai.storyteller.solidcraft` |
| AppImage | Uses the Linux 256 px image and `.DirIcon` alias |
| FreeBSD | Shares Linux hicolor images and freedesktop metadata |
| Web | HTML/Trunk stays in `apps/solidcraft-web/`; copies Japanese font/licence to `fonts/` and uses the collected CSS; no favicon/PWA icon currently exists |
| Docs and link previews | `xtask book` copies the shared card to the existing `/solidcraft/og.png` public path |
## Code and provenance
Resources remain compiled by their owning layer through existing APIs:
`ui-egui::icons::paint` and `Tokens` at L5, model presets in L2 `doc`, and sketch
fonts at L1. State, projection, interaction, commands and GPU bindings stay in
their current modules. Embedded data uses `CARGO_MANIFEST_DIR`; application code
needs no runtime resource registry or repository lookup.
`cargo xtask assets` requires an exact attribution row for every collected file
except Markdown documentation, and continues checking file assets elsewhere in
the repository. This includes native work, Rust masters, catalogs, shaders and
platform templates. Packaging selects explicit current files and fails when
required inputs are absent. Archived artwork has no production consumers.
Dependency-owned egui fonts and vendored renderer shaders stay with their
libraries; their licences remain in `THIRD-PARTY-LICENSES.txt` and `ATTRIBUTION.md`.
System fonts, optional packs, user fonts/canvases/decals/materials and native OS
controls retain external ownership. Root licence/notice texts stay canonical.
+23
View File
@@ -0,0 +1,23 @@
//! Built-in model appearances: names, sRGB colours and metalness.
pub const LIBRARY: [(&str, [u8; 3], f64); 20] = [
("Aluminum - Satin", [204, 208, 214], 1.0),
("Aluminum - Anodized Black", [44, 46, 50], 1.0),
("Aluminum - Anodized Blue", [52, 92, 160], 1.0),
("Steel - Satin", [158, 164, 172], 1.0),
("Stainless Steel - Brushed", [186, 191, 198], 1.0),
("Cast Iron", [104, 104, 110], 1.0),
("Brass - Polished", [208, 172, 88], 1.0),
("Copper", [204, 122, 80], 1.0),
("Titanium", [150, 148, 158], 1.0),
("ABS - White", [236, 236, 230], 1.0),
("ABS - Black", [36, 36, 38], 1.0),
("ABS - Red", [196, 40, 40], 1.0),
("ABS - Blue", [40, 90, 186], 1.0),
("ABS - Yellow", [236, 196, 40], 1.0),
("ABS - Green", [52, 150, 80], 1.0),
("Rubber - Black", [28, 28, 28], 1.0),
("Glass - Clear", [190, 220, 228], 0.25),
("Glass - Smoked", [90, 96, 104], 0.45),
("Wood - Oak", [190, 146, 92], 1.0),
("Paint - Gloss White", [246, 246, 244], 1.0),
];
File renamed without changes.
File renamed without changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

File renamed without changes.
File renamed without changes.
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 944 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,48 @@
//! Original executable ICO encoder: DIB images and transparency masks.
use super::app_icon;
pub fn encode() -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let sizes = [16usize, 24, 32, 48, 64, 128, 256];
let mut images = Vec::new();
for size in sizes {
let rgba = app_icon::rgba(size)?;
// ICO DIB: bottom-up BGRA plus a padded one-bit transparency mask.
let mask_stride = size.div_ceil(32) * 4;
let mut dib = Vec::new();
dib.extend_from_slice(&40u32.to_le_bytes());
dib.extend_from_slice(&(size as i32).to_le_bytes());
dib.extend_from_slice(&((size * 2) as i32).to_le_bytes());
dib.extend_from_slice(&1u16.to_le_bytes());
dib.extend_from_slice(&32u16.to_le_bytes());
dib.extend_from_slice(&[0; 24]);
for row in rgba.chunks_exact(size * 4).rev() {
for p in row.as_chunks::<4>().0.iter() {
dib.extend_from_slice(&[p[2], p[1], p[0], p[3]]);
}
}
for row in rgba.chunks_exact(size * 4).rev() {
let mut mask = vec![0u8; mask_stride];
for (x, p) in row.as_chunks::<4>().0.iter().enumerate() {
if p[3] == 0 {
mask[x / 8] |= 0x80 >> (x % 8);
}
}
dib.extend_from_slice(&mask);
}
images.push((size, dib));
}
let mut ico = vec![0, 0, 1, 0];
ico.extend_from_slice(&(images.len() as u16).to_le_bytes());
let mut offset = 6 + 16 * images.len();
for (size, data) in &images {
let dim = if *size == 256 { 0 } else { *size as u8 };
ico.extend_from_slice(&[dim, dim, 0, 0, 1, 0, 32, 0]);
ico.extend_from_slice(&(data.len() as u32).to_le_bytes());
ico.extend_from_slice(&(offset as u32).to_le_bytes());
offset += data.len();
}
for (_, data) in images {
ico.extend_from_slice(&data);
}
Ok(ico)
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+22
View File
@@ -0,0 +1,22 @@
# UI artwork
Edit the Rust drawing masters in `source/`; the UI crate compiles them through
`crates/ui-egui/src/icons.rs`. Use the 24 × 24 grid and semantic ink/fill/accent
colours. Icon IDs and their geometry live together in `source/icons.rs`;
unknown IDs retain the existing fallback. Add new artwork there without
maintaining a separate ID registry.
| Source | Artwork | Consumers |
| --- | --- | --- |
| `icons.rs` | Toolbar, browser, menu and constraint icons | `icons::paint` |
| `glyphs.rs` | Brand mark, captions, transport, badges, pin, caret, disclosure, radial centre and removal cross | toolbar, titlebar, timeline, browser, menus |
| `navigation.rs` | Signed axes, cube faces/labels and arrows | viewport/navigation cube |
| `markers.rs` | Constraint mappings/letters, snap markers and dimension arrowheads | sketch, assembly and dimensions |
| `pointer.rs` | Agent pointer and tag | agent cursor |
Widget state, layout, projection, picking and commands stay with their widgets.
Review drawing changes in the actual application in both light and dark themes.
No separate native design projects were available for this set. Preserve any
future native work and linked inputs under `source/work/`, with attribution.
Original artwork is MIT OR Apache-2.0; caption geometry retains VectorCraft
attribution in the root `ATTRIBUTION.md`.
+158
View File
@@ -0,0 +1,158 @@
//! Collected glyph artwork. Caption geometry adapted from VectorCraft (MIT OR Apache-2.0).
use crate::titlebar::Caption;
use egui::{Color32, Pos2, Rect, Stroke, Ui, pos2, vec2};
pub(crate) fn transport_glyph(p: &egui::Painter, c: egui::Pos2, what: &str, col: Color32) {
let tri = |dir: f32, dx: f32| {
let pts = vec![pos2(c.x + dx - 4.0 * dir, c.y - 5.0), pos2(c.x + dx + 4.0 * dir, c.y), pos2(c.x + dx - 4.0 * dir, c.y + 5.0)];
p.add(egui::Shape::convex_polygon(pts, col, Stroke::NONE));
};
let bar = |dx: f32| p.line_segment([pos2(c.x + dx, c.y - 5.0), pos2(c.x + dx, c.y + 5.0)], Stroke::new(1.6, col));
match what {
"first" => {
bar(-5.0);
tri(-1.0, 1.0);
}
"back" => tri(-1.0, 0.0),
"forward" => tri(1.0, 0.0),
_ => {
tri(1.0, -1.0);
bar(5.0);
}
}
}
pub(crate) fn paint_glyph(ui: &Ui, c: Caption, maximized: bool, at: egui::Pos2, s: Stroke) {
let p = ui.painter();
let g = Rect::from_center_size(at, vec2(10.0, 10.0));
match c {
Caption::Minimize => {
p.line_segment([g.left_center(), g.right_center()], s);
}
// Restore: a front square with the corner of the one behind it.
Caption::Maximize if maximized => {
let front = Rect::from_min_max(g.min + vec2(0.0, 2.0), g.max - vec2(2.0, 0.0));
p.rect_stroke(front, 0.0, s, egui::StrokeKind::Middle);
p.line(vec![g.min + vec2(2.0, 2.0), g.min + vec2(2.0, 0.0), g.right_top(), g.max - vec2(0.0, 2.0), g.max - vec2(2.0, 2.0)], s);
}
Caption::Maximize => {
p.rect_stroke(g, 0.0, s, egui::StrokeKind::Middle);
}
Caption::Close => {
p.line_segment([g.left_top(), g.right_bottom()], s);
p.line_segment([g.right_top(), g.left_bottom()], s);
}
}
}
pub(crate) fn badge(p: &egui::Painter, r: Rect, name: &str, c: Color32) {
let s = Stroke::new(1.3, c);
match name {
"lock" => {
let body = Rect::from_min_max(pos2(r.left() + 1.5, r.center().y - 0.5), pos2(r.right() - 1.5, r.bottom() - 0.5));
p.rect_filled(body, 1.5, c);
let top = pos2(r.center().x, r.top() + 4.0);
p.add(egui::Shape::line(
(0..=8)
.map(|k| {
let a = std::f32::consts::PI * (k as f32 / 8.0);
pos2(top.x - 3.2 * a.cos(), top.y - 3.2 * a.sin() + 1.0)
})
.chain([pos2(top.x + 3.2, body.top())])
.collect(),
s,
));
p.line_segment([pos2(top.x - 3.2, top.y + 1.0), pos2(top.x - 3.2, body.top())], s);
}
"pin" => {
let head = pos2(r.center().x + 2.0, r.top() + 4.0);
p.circle_filled(head, 3.2, c);
p.line_segment([head, pos2(r.left() + 1.5, r.bottom() - 1.0)], Stroke::new(1.6, c));
}
_ => {}
}
}
pub(crate) fn pin_icon(p: &egui::Painter, r: Rect, on: bool, col: Color32) {
let c = r.center();
let (sin, cos) = if on { (0.0, 1.0) } else { std::f32::consts::FRAC_PI_4.sin_cos() };
// Local coordinates: the pin points down, its head on top.
let at = |x: f32, y: f32| c + vec2(x * cos - y * sin, x * sin + y * cos);
let head = vec![at(-2.5, -7.0), at(2.5, -7.0), at(3.0, -2.0), at(-3.0, -2.0)];
let stroke = Stroke::new(1.4, col);
if on {
p.add(egui::Shape::convex_polygon(head, col, stroke));
} else {
p.add(egui::Shape::closed_line(head, stroke));
}
p.line_segment([at(-5.5, -2.0), at(5.5, -2.0)], stroke);
p.line_segment([at(0.0, -2.0), at(0.0, 7.0)], stroke);
}
pub(crate) fn caret(p: &egui::Painter, c: egui::Pos2, col: Color32) {
p.add(egui::Shape::convex_polygon(vec![pos2(c.x - 3.5, c.y - 2.0), pos2(c.x + 3.5, c.y - 2.0), pos2(c.x, c.y + 2.5)], col, Stroke::NONE));
}
pub(crate) fn brand_mark(p: &egui::Painter, c: Pos2) {
let s = 20.0 / 412.217;
let m = |pts: &[(f32, f32)]| -> Vec<egui::Pos2> { pts.iter().map(|&(x, y)| pos2(c.x + (x - 178.5) * s, c.y + (y - 206.1) * s)).collect() };
// egui only fills convex shapes, so each face is a few overlapping convex pieces.
let face = |col: Color32, pts: &[(f32, f32)]| {
p.add(egui::Shape::convex_polygon(m(pts), col, Stroke::NONE));
};
let (light, mid, dark) = (Color32::from_rgb(96, 158, 236), Color32::from_rgb(58, 136, 238), Color32::from_rgb(55, 108, 197));
face(light, &[(3.5, 105.9), (177.0, 5.8), (263.4, 55.6), (263.8, 156.0), (177.0, 406.4)]);
face(light, &[(177.0, 206.1), (263.8, 156.0), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)]);
face(mid, &[(3.5, 105.9), (177.0, 206.1), (177.0, 406.4)]);
face(mid, &[(3.5, 105.9), (3.5, 205.7), (90.3, 256.2), (177.0, 206.1)]);
face(mid, &[(177.0, 206.1), (90.3, 256.2), (90.0, 356.2), (177.0, 406.4)]);
face(dark, &[(264.1, 155.8), (177.0, 206.1), (177.0, 406.4), (263.8, 256.2)]);
face(dark, &[(263.8, 256.2), (350.5, 206.2), (350.5, 306.3), (177.0, 406.4)]);
face(dark, &[(177.0, 206.1), (177.0, 406.4), (350.5, 306.3)]);
face(dark, &[(263.8, 55.9), (263.8, 156.0), (177.1, 105.9)]);
// White edge lines.
let edge = Stroke::new(10.0 * s, Color32::WHITE);
let line = |pts: &[(f32, f32)]| {
p.add(egui::Shape::line(m(pts), edge));
};
line(&[(263.8, 55.9), (177.1, 105.9), (263.8, 156.0), (263.8, 256.2), (350.6, 206.1)]);
line(&[(5.0, 105.9), (178.5, 206.1), (178.5, 406.4)]);
line(&[(177.0, 206.1), (263.8, 156.0)]);
p.add(egui::Shape::closed_line(
m(&[
(178.5, 5.8),
(5.0, 105.9),
(5.0, 206.1),
(91.7, 256.2),
(91.7, 356.4),
(178.5, 406.4),
(352.0, 306.3),
(352.0, 206.1),
(265.2, 156.0),
(265.2, 55.9),
]),
edge,
));
}
pub(crate) fn cross(p: &egui::Painter, r: Rect, s: Stroke) {
p.line_segment([r.left_top(), r.right_bottom()], s);
p.line_segment([r.right_top(), r.left_bottom()], s);
}
pub(crate) fn radial_centre(p: &egui::Painter, c: Pos2, direction: Option<(egui::Vec2, bool)>, t: crate::theme::Tokens) {
p.circle(c, 15.0, t.panel.gamma_multiply(0.92), Stroke::new(1.0, t.border));
if let Some((d, enabled)) = direction {
p.line_segment([c + d * 6.0, c + d * 15.0], Stroke::new(3.0, if enabled { t.accent } else { t.border }));
} else {
p.circle_filled(c, 3.0, t.text_dim);
}
}
pub(crate) fn disclosure(p: &egui::Painter, ar: Rect, open: bool, ink: Color32) {
let pts = if open {
vec![pos2(ar.left() + 2.0, ar.top() + 4.0), pos2(ar.right() - 2.0, ar.top() + 4.0), pos2(ar.center().x, ar.bottom() - 3.0)]
} else {
vec![pos2(ar.left() + 4.0, ar.top() + 2.0), pos2(ar.right() - 3.0, ar.center().y), pos2(ar.left() + 4.0, ar.bottom() - 2.0)]
};
p.add(egui::Shape::convex_polygon(pts, ink, Stroke::NONE));
}
File diff suppressed because it is too large. Load diff
+86
View File
@@ -0,0 +1,86 @@
//! Constraint, assembly snap and dimension marker artwork.
use crate::dialogs_assembly::SnapKind;
use crate::theme::Tokens;
use egui::{Color32, Pos2, Stroke, vec2};
pub(crate) fn snap(painter: &egui::Painter, c: Pos2, tip: Option<Pos2>, kind: SnapKind, col: Color32, tag: Option<&str>) {
// A dark halo under every stroke keeps the glyph readable on light and dark faces alike.
let halo = Stroke::new(4.5, Color32::from_black_alpha(150));
let white = Stroke::new(1.5, Color32::WHITE);
if let Some(tip) = tip {
let d = (tip - c).normalized();
let n = vec2(-d.y, d.x);
let head = vec![tip + d * 2.0, tip - d * 9.0 + n * 5.0, tip - d * 9.0 - n * 5.0];
painter.line_segment([c, tip], halo);
painter.add(egui::Shape::convex_polygon(head.clone(), Color32::TRANSPARENT, halo));
painter.line_segment([c, tip], Stroke::new(2.0, col));
painter.add(egui::Shape::convex_polygon(head, col, Stroke::NONE));
}
let shape = |fill: Color32, stroke: Stroke, grow: f32| -> egui::Shape {
match kind {
SnapKind::FaceCenter => egui::Shape::rect_filled(egui::Rect::from_center_size(c, vec2(12.0 + grow, 12.0 + grow)), 1.5, fill),
SnapKind::CircleCenter => egui::Shape::circle_filled(c, 7.0 + grow / 2.0, fill),
SnapKind::EdgeMid => {
let r = 8.0 + grow / 2.0;
egui::Shape::convex_polygon(vec![c + vec2(0.0, -r), c + vec2(r * 0.93, r * 0.7), c + vec2(-r * 0.93, r * 0.7)], fill, stroke)
}
SnapKind::Vertex => {
let r = 7.5 + grow / 2.0;
egui::Shape::convex_polygon(vec![c + vec2(0.0, -r), c + vec2(r, 0.0), c + vec2(0.0, r), c + vec2(-r, 0.0)], fill, stroke)
}
}
};
painter.add(shape(Color32::from_black_alpha(150), Stroke::NONE, 4.0));
painter.add(shape(col, Stroke::NONE, 0.0));
match kind {
SnapKind::FaceCenter => {
painter.rect_stroke(egui::Rect::from_center_size(c, vec2(12.0, 12.0)), 1.5, white, egui::StrokeKind::Middle);
}
SnapKind::CircleCenter => {
painter.circle_stroke(c, 7.0, white);
painter.line_segment([c - vec2(4.5, 0.0), c + vec2(4.5, 0.0)], Stroke::new(1.2, Color32::WHITE));
painter.line_segment([c - vec2(0.0, 4.5), c + vec2(0.0, 4.5)], Stroke::new(1.2, Color32::WHITE));
}
SnapKind::EdgeMid | SnapKind::Vertex => {
painter.add(shape(Color32::TRANSPARENT, white, 0.0));
}
}
// What it snaps to (hovered), or which component it is (picked): a small label.
if let Some(tag) = tag {
let t = Tokens::get();
let font = egui::FontId::proportional(11.5);
let g = painter.layout_no_wrap(tag.to_string(), font, t.text);
let at = c + vec2(12.0, 10.0);
let r = egui::Rect::from_min_size(at, g.size()).expand2(vec2(5.0, 2.0));
painter.rect(r, 3.0, t.panel.gamma_multiply(0.92), Stroke::new(1.0, col), egui::StrokeKind::Inside);
painter.galley(at, g, t.text);
}
}
pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
Some(match name {
"Coincident" => "c_coincident",
"Parallel" => "c_parallel",
"Perpendicular" => "c_perpendicular",
"Collinear" => "c_collinear",
"Tangent" => "c_tangent",
"Smooth" => "c_smooth",
"Equal" => "c_equal",
"Concentric" => "c_concentric",
"MidPoint" => "c_midpoint",
"Symmetry" => "c_symmetry",
"Fix" => "c_fix",
_ => return None,
})
}
pub(crate) fn glyph_letter(name: &str) -> &'static str {
match name {
"Horizontal" => "H",
"Vertical" => "V",
_ => "?",
}
}
pub(crate) fn dimension_arrow(painter: &egui::Painter, t: Pos2, back: Pos2, col: Color32) {
let d = (t - back).normalized();
let n = vec2(-d.y, d.x);
painter.add(egui::Shape::convex_polygon(vec![t, t - d * 9.0 + n * 3.0, t - d * 9.0 - n * 3.0], col, Stroke::NONE));
}
+167
View File
@@ -0,0 +1,167 @@
//! Navigation drawing definitions. Camera projection and picking remain in the UI widgets.
use crate::theme::Tokens;
use egui::{Align2, Color32, FontId, Pos2, Rect, Shape, Stroke, pos2, vec2};
use solidcraft_engine::geom::Vec3;
pub(crate) struct Facet {
pub(crate) direction: Vec3,
pub(crate) points: Vec<Pos2>,
pub(crate) outer_edges: Vec<bool>,
pub(crate) centre: Pos2,
pub(crate) label: String,
pub(crate) text_axes: Option<(egui::Vec2, egui::Vec2)>,
pub(crate) depth: f64,
}
// Private marker for label meshes with atlas-pixel UVs. The output hook replaces
// it with the font texture before any renderer sees the frame.
pub(crate) const LABEL_PIXELS: egui::TextureId = egui::TextureId::User(u64::from_le_bytes(*b"cubetext"));
struct LabelAtlas;
impl egui::plugin::Plugin for LabelAtlas {
fn debug_name(&self) -> &'static str {
"navigation cube label atlas"
}
fn output_hook(&mut self, ctx: &egui::Context, output: &mut egui::FullOutput) {
let size = ctx.fonts(|f| f.font_image_size());
let normalize = vec2(1.0 / size[0].max(1) as f32, 1.0 / size[1].max(1) as f32);
for shape in &mut output.shapes {
if let Shape::Mesh(mesh) = &mut shape.shape
&& mesh.texture_id == LABEL_PIXELS
{
let mesh = std::sync::Arc::make_mut(mesh);
mesh.texture_id = egui::TextureId::default();
for v in &mut mesh.vertices {
v.uv = (v.uv.to_vec2() * normalize).to_pos2();
}
}
}
}
}
/// Warp the font mesh into the projected face, so labels turn with the model.
pub(crate) fn face_text(painter: &egui::Painter, face: &Facet, ink: Color32, font_size: f32) {
let Some((r, down)) = face.text_axes else { return };
let opacity = ((face.depth - 0.18) / 0.17).clamp(0.0, 1.0) as f32;
if opacity <= 0.0 {
return;
}
let ink = ink.gamma_multiply(opacity);
let galley = painter.layout_no_wrap(face.label.clone(), FontId::proportional(font_size), ink);
let centre = galley.rect.center();
// Keep atlas-pixel UVs until the frame is complete: another label or widget
// can grow the atlas after this mesh is created, invalidating early UVs.
painter.ctx().add_plugin(LabelAtlas);
let text = Shape::Text(egui::epaint::TextShape::new(Pos2::ZERO, galley, ink));
let atlas = painter.ctx().fonts(|f| f.font_image_size());
let primitives =
painter.ctx().tessellate(vec![egui::epaint::ClippedShape { clip_rect: Rect::EVERYTHING, shape: text }], painter.ctx().pixels_per_point());
for primitive in primitives {
if let egui::epaint::Primitive::Mesh(mut mesh) = primitive.primitive {
mesh.texture_id = LABEL_PIXELS;
for v in &mut mesh.vertices {
v.uv = pos2(v.uv.x * atlas[0] as f32, v.uv.y * atlas[1] as f32);
let local = v.pos - centre;
v.pos = face.centre + r * local.x + down * (local.y * 1.35);
}
painter.add(Shape::mesh(mesh));
}
}
}
pub(crate) fn paint_cube(painter: &egui::Painter, faces: &[Facet], hovered: Option<usize>, t: Tokens, labels: bool, scale: f32) {
// A shared, opaque triangle mesh has no antialiasing fringes between facets.
// Per-polygon fringes and miter joins overlap as a face becomes edge-on.
let mut fill_mesh = egui::epaint::Mesh::default();
let mut edges: Vec<(Pos2, Pos2, usize, bool)> = Vec::new();
let hovered_direction = hovered.and_then(|i| faces.get(i)).map(|f| f.direction);
for face in faces {
let base = t.cube_face;
let shade = (0.72 + 0.28 * face.depth.min(1.0)) as f32;
let highlighted = hovered_direction == Some(face.direction);
let fill = if highlighted {
t.cube_hover
} else {
Color32::from_rgb((f32::from(base.r()) * shade) as u8, (f32::from(base.g()) * shade) as u8, (f32::from(base.b()) * shade) as u8)
};
let centre = fill_mesh.vertices.len() as u32;
fill_mesh.colored_vertex(face.centre, fill);
for &p in &face.points {
fill_mesh.colored_vertex(p, fill);
}
for (j, (&a, &b)) in face.points.iter().zip(face.points.iter().cycle().skip(1)).take(face.points.len()).enumerate() {
fill_mesh.add_triangle(centre, centre + 1 + j as u32, centre + 1 + ((j + 1) % face.points.len()) as u32);
// Coplanar picking regions have no visible seams. Only actual cube
// edges and the hovered target's boundary receive an outline.
let outer = face.outer_edges.get(j).copied().unwrap_or(false);
if !highlighted && !outer {
continue;
}
if let Some(edge) = edges
.iter_mut()
.find(|(p, q, _, _)| (p.distance_sq(a) < 1e-6 && q.distance_sq(b) < 1e-6) || (p.distance_sq(b) < 1e-6 && q.distance_sq(a) < 1e-6))
{
edge.2 += usize::from(highlighted);
edge.3 |= outer;
} else {
edges.push((a, b, usize::from(highlighted), outer));
}
}
}
painter.add(Shape::mesh(fill_mesh));
for (a, b, highlights, outer) in edges {
// Two highlighted coplanar patches share an internal subdivision, not
// a target boundary. Actual cube edges remain visible across faces.
if !outer && highlights != 1 {
continue;
}
let highlighted = highlights > 0;
painter.line_segment([a, b], Stroke::new(1.0, if highlighted { t.accent } else { t.cube_border }));
}
if labels {
for face in faces {
face_text(painter, face, t.cube_text, 14.0 * scale);
}
}
}
pub(crate) fn arrow(painter: &egui::Painter, at: Pos2, direction: egui::Vec2, hover: bool, t: Tokens) {
let side = vec2(-direction.y, direction.x);
painter.add(Shape::convex_polygon(
vec![at + direction * 7.0, at - direction * 5.0 + side * 7.0, at - direction * 5.0 - side * 7.0],
if hover { t.accent_soft } else { t.cube_face },
Stroke::new(1.0, if hover { t.accent } else { t.cube_border }),
));
}
pub(crate) fn curved_arrow(painter: &egui::Painter, at: Pos2, clockwise: bool, hover: bool, t: Tokens, scale: f32) {
let (start, end) =
if clockwise { (-std::f32::consts::FRAC_PI_4 + 0.12, 0.0) } else { (-std::f32::consts::FRAC_PI_4 - 0.12, -std::f32::consts::FRAC_PI_2) };
let points: Vec<_> = (0..=16)
.map(|i| {
let angle = egui::lerp(start..=end, f32::from(i as u8) / 16.0);
at + vec2(angle.cos(), angle.sin()) * (32.0 * scale)
})
.collect();
painter.add(Shape::line(points.clone(), Stroke::new(5.0 * scale, if hover { t.accent } else { t.cube_border })));
painter.add(Shape::line(points.clone(), Stroke::new(3.0 * scale, if hover { t.accent_soft } else { t.cube_face })));
if let Some(tip) = points.last() {
let sign = if clockwise { 1.0 } else { -1.0 };
arrow(painter, *tip, vec2(-end.sin(), end.cos()) * (sign * scale), hover, t);
}
}
pub(crate) const AXIS_RADIUS: f32 = 11.0;
pub(crate) fn axis_tip(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, at: Pos2, depth: f64, hovered: bool, t: Tokens) {
let col = if d.x != 0.0 {
solidcraft_engine::view::colors::AXIS_X
} else if d.y != 0.0 {
solidcraft_engine::view::colors::AXIS_Y
} else {
solidcraft_engine::view::colors::AXIS_Z
};
let col = Color32::from_rgb(col.0, col.1, col.2);
let positive = !name.starts_with('-');
let ink = if depth < -0.01 { col.gamma_multiply(0.55) } else { col };
painter.line_segment([c, at], Stroke::new(if positive { 2.0 } else { 1.0 }, ink));
painter.circle_filled(at, AXIS_RADIUS, if hovered { t.accent_soft } else { t.overlay });
painter.circle_stroke(at, AXIS_RADIUS, Stroke::new(if hovered { 2.0 } else { 1.0 }, if hovered { t.accent } else { ink }));
painter.text(at, Align2::CENTER_CENTER, name.to_uppercase(), FontId::proportional(11.0), ink);
}
+14
View File
@@ -0,0 +1,14 @@
//! Original agent pointer and label. Animation stays with the UI controller.
use egui::{Color32, FontId, Pos2, Shape, Stroke, vec2};
pub(crate) const AGENT: Color32 = Color32::from_rgb(168, 85, 247);
pub(crate) fn paint(painter: &egui::Painter, p: Pos2) {
// An arrow pointer, tip at `p`.
let pts: Vec<Pos2> =
[(0.0, 0.0), (0.0, 17.0), (4.5, 13.0), (7.5, 20.0), (10.5, 18.8), (7.5, 12.0), (13.0, 12.0)].iter().map(|(x, y)| p + vec2(*x, *y)).collect();
painter.add(Shape::convex_polygon(pts.clone(), AGENT, Stroke::NONE));
painter.add(Shape::closed_line(pts, Stroke::new(1.5, Color32::WHITE)));
let label = painter.layout_no_wrap("Agent".into(), FontId::proportional(11.0), Color32::WHITE);
let r = egui::Rect::from_min_size(p + vec2(15.0, 18.0), label.size() + vec2(10.0, 4.0));
painter.rect_filled(r, 4.0, AGENT);
painter.galley(r.min + vec2(5.0, 2.0), label, Color32::WHITE);
}
+701
View File
@@ -0,0 +1,701 @@
{
"browser.body_name": "Körper {number}",
"browser.component_name": "Komponente {number}",
"browser.parameters": "Parameter ({count})",
"browser.precision": "Genauigkeit: {precision}",
"browser.sketch_name": "Skizze {number}",
"browser.units": "Einheiten: {units}",
"dialog.custom_axis": "↖ Eigene Achse",
"dialog.edit_title": "{title} bearbeiten",
"dialog.select": "↖ Auswählen",
"dialog.selected_count": "↖ {count} ausgewählt",
"dialog.step": "Schritt",
"dialog.selection_step": "{step} von {total}: {input} auswählen",
"dialog.selection_guidance": "Wählen Sie eine Eingabe aus, um sie zu ändern. Klicken Sie auf ausgewählte Objekte, um sie zu entfernen; Strg/Cmd-Klick schaltet sie ebenfalls um. Prüfen Sie die Vorschau und drücken Sie dann OK.",
"document.save_changes": "Änderungen in {name} speichern?",
"document.unsaved_changes": "{name} enthält ungespeicherte Änderungen. Wenn Sie nicht speichern, gehen sie verloren.",
"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.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",
"sketch.degrees_of_freedom": "{count} Freiheitsgrade",
"sketch.draw_curves": "Als {linetype} zeichnen",
"sketch.make_selection": "Auswahl als {linetype} markieren",
"ui.1_degree_of_freedom": "1 Freiheitsgrad",
"ui.3d_sketch": "3D-Skizze",
"ui.about": "Über",
"ui.activate": "Aktivieren",
"ui.active_component": "Aktive Komponente",
"ui.agent_cursor": "Agent-Cursor",
"ui.agent_cursor_speed": "Agent-Cursor-Geschwindigkeit",
"ui.all": "Alle",
"ui.analysis": "Analyse",
"ui.angle": "Winkel",
"ui.angle_decimals": "Winkel-Dezimalstellen",
"ui.animate_view_changes": "Ansichtsänderungen animieren",
"ui.animation": "Animation",
"ui.animation_speed": "Animationsgeschwindigkeit",
"ui.anti_aliasing": "Kantenglättung",
"ui.apply": "Anwenden",
"ui.arc": "Kreisbogen",
"ui.assemble": "Zusammenbau",
"ui.autosave_every": "Autospeichern alle",
"ui.axes": "Achsen",
"ui.axis": "Achse",
"ui.base_point": "Basispunkt",
"ui.bodies": "Körper",
"ui.box": "Block",
"ui.break": "Unterbrechen",
"ui.cancel": "Abbrechen",
"ui.canvases": "Kanvas",
"ui.center": "Mittelpunkt",
"ui.centerline": "Mittellinie",
"ui.chain": "Kette",
"ui.chamfer": "Abschrägung",
"ui.chamfer_type": "Fasentyp",
"ui.change_workspace": "Arbeitsbereich wechseln",
"ui.chord_length": "Sehnenlänge",
"ui.curvature_g2": "Krümmungsstetig (G2)",
"ui.equivalent_radius": "Äquivalenter Radius",
"ui.circle": "Kreis",
"ui.circular_pattern": "Radiales Muster",
"ui.clear_recent": "Zuletzt verwendet leeren",
"ui.clear_the_selection": "Auswahl aufheben",
"ui.close": "Schließen",
"ui.coil": "Schraube",
"ui.coincident": "Koinzident",
"ui.collapse": "Einklappen",
"ui.collinear": "Kollinear",
"ui.combine": "Kombinieren",
"ui.coming_later": "bald verfügbar",
"ui.concentric": "Konzentrisch",
"ui.configurations": "Konfigurationen",
"ui.conic_curve": "Kegelschnitt",
"ui.constant": "Konstant",
"ui.constraints_conflict": "Bedingungen konfliktreich",
"ui.construct": "Konstruktion",
"ui.construction": "Konstruktionslinie",
"ui.copy": "Kopieren",
"ui.corners": "Ecken",
"ui.corner": "Ecke",
"ui.counterbore": "Tiefenbohrung",
"ui.countersink": "Senkung",
"ui.create": "Erstellen",
"ui.create_copy": "Kopie erstellen",
"ui.create_sketch": "Skizze erstellen",
"ui.current": "aktuell",
"ui.curvature": "Krümmung",
"ui.curves": "Kurven",
"ui.cut": "Ausschneiden",
"ui.cylinder": "Zylinder",
"ui.dark": "Dunkel",
"ui.default_modelling_orientation": "Standard-Modellierungsorientierung",
"ui.degrees_of_freedom": "Freiheitsgrade",
"ui.delete": "Löschen",
"ui.depth": "Tiefe",
"ui.design": "Design",
"ui.diameter": "Durchmesser",
"ui.dimension": "Maß",
"ui.direction": "Richtung",
"ui.distance": "Abstand",
"ui.distance_1": "Abstand 1",
"ui.distance_2": "Abstand 2",
"ui.document_settings": "Dokumenteneinstellungen",
"ui.don_t_save": "Nicht speichern",
"ui.draft": "Abziehen",
"ui.drawing": "Zeichnung",
"ui.drill_point": "Bohrpunkt",
"ui.edit": "Bearbeiten",
"ui.edit_section_analysis": "Querschnittsanalyse bearbeiten",
"ui.ellipse": "Ellipse",
"ui.end_radius": "Endradius",
"ui.entities": "Elemente",
"ui.equal": "Gleich",
"ui.expand": "Aufklappen",
"ui.export_ellipsis": "Exportieren...",
"ui.extend": "Verlängern",
"ui.extent_type": "Ausdehnungstyp",
"ui.extrude": "Extrudieren",
"ui.factor": "Faktor",
"ui.feature_history": "Feature-Verlauf",
"ui.feature_options": "Funktionsoptionen",
"ui.features": "Funktionen",
"ui.file": "Datei",
"ui.fillet": "Vollrundung",
"ui.finish_sketch": "Skizze beenden",
"ui.fix_unfix": "Fixieren/Lösen",
"ui.flat": "Flach",
"ui.flip": "Umkehren",
"ui.front": "Vorne",
"ui.fully_constrained": "Vollständig bestimmt",
"ui.general": "Allgemein",
"ui.graphics": "Grafik",
"ui.heading_assemble": "ZUSAMMENBAU",
"ui.heading_box": "BLOCK",
"ui.heading_chamfer": "ABSCHRÄGUNG",
"ui.heading_circular_pattern": "RADIALES MUSTER",
"ui.heading_coil": "SCHRAUBE",
"ui.heading_combine": "KOMBINIEREN",
"ui.heading_construct": "KONSTRUKTION",
"ui.heading_create": "ERSTELLEN",
"ui.heading_create_sketch": "SKIZZE ERSTELLEN",
"ui.heading_cylinder": "ZYLINDER",
"ui.heading_design": "DESIGN",
"ui.heading_draft": "ABZIEHEN",
"ui.heading_edit": "BEARBEITEN",
"ui.heading_extrude": "EXTRUDIEREN",
"ui.heading_fillet": "VOLLRUNDUNG",
"ui.heading_finish_sketch": "SKIZZE BEENDEN",
"ui.heading_front": "VORNE",
"ui.heading_hole": "BOHRUNG",
"ui.heading_home": "START",
"ui.heading_insert": "EINFÜGEN",
"ui.heading_inspect": "PRÜFEN",
"ui.heading_loft": "LOFT",
"ui.heading_measure": "Messen",
"ui.heading_mesh": "MESH",
"ui.heading_mirror": "SPIEGELN",
"ui.heading_modify": "ÄNDERN",
"ui.heading_move": "VERSCHIEBEN",
"ui.heading_offset": "ABSTAND",
"ui.heading_move_copy": "VERSCHIEBEN/KOPIEREN",
"ui.heading_offset_plane": "ABSTANDSEBENE",
"ui.heading_ok": "OK",
"ui.heading_parameters": "PARAMETER",
"ui.heading_pattern_on_path": "PFADMUSTER",
"ui.heading_pipe": "ROHR",
"ui.heading_plane_at_angle": "EBENE IM WINKEL",
"ui.heading_plastic": "PLASTIK",
"ui.heading_rectangular_pattern": "RECHTECKMUSTER",
"ui.heading_revolve": "DREHEN",
"ui.heading_right": "RECHTS",
"ui.heading_scale": "SKALIEREN",
"ui.heading_section_analysis": "QUERSCHNITTSANALYSE",
"ui.heading_select": "AUSWÄHLEN",
"ui.heading_sheet_metal": "BLECH",
"ui.heading_shell": "HÜLLE",
"ui.heading_sketch": "SKIZZE",
"ui.heading_sketch_palette": "SKIZZENPALETTE",
"ui.heading_sketch_scale": "SKIZZE SKALIEREN",
"ui.heading_solid": "KÖRPER",
"ui.heading_sphere": "SPHÄRE",
"ui.heading_split_body": "KÖRPER TRENNEN",
"ui.heading_surface": "FLÄCHE",
"ui.heading_sweep": "FÜHREN",
"ui.heading_thread": "GEWINDE",
"ui.heading_top": "OBEN",
"ui.heading_torus": "TORUS",
"ui.help": "Hilfe",
"ui.hide": "Ausblenden",
"ui.hole": "Bohrung",
"ui.hole_tap_type": "Gewindetyp",
"ui.hole_type": "Bohrungstyp",
"ui.home": "Start",
"ui.horizontal_vertical": "Horizontal/Vertikal",
"ui.insert": "Einfügen",
"ui.inspect": "Prüfen",
"ui.intersect": "Schnitt",
"ui.join": "Verbinden",
"ui.joints": "Gelenke",
"ui.keyboard_shortcuts": "Tastenkürzel",
"ui.language": "Sprache",
"ui.length_decimals": "Längen-Dezimalstellen",
"ui.light": "Hell",
"ui.line": "Linie",
"ui.linetype": "Linientyp",
"ui.loft": "Loft",
"ui.look_at": "Ansehen",
"ui.manufacture": "Fertigung",
"ui.measure": "Messen",
"ui.midpoint": "Mittelpunkt",
"ui.mirror": "Spiegeln",
"ui.mirror_line": "Spiegelachse",
"ui.modify": "Ändern",
"ui.move": "Verschieben",
"ui.multiple": "Mehrfach",
"ui.named_views": "Benannte Ansichten",
"ui.navigation": "Navigation",
"ui.needs_design": "{command}: Öffnen Sie zuerst ein Design",
"ui.new_body": "Neuer Körper",
"ui.new_design": "Neues Design",
"ui.normal": "Normal",
"ui.object_type": "Objekttyp",
"ui.objects": "Objekte",
"ui.offer_to_stand_up_y_up_files": "Y-hoch-Dateien aufrecht anbieten",
"ui.offset": "Abstand",
"ui.offset_plane": "Abstandsebene",
"ui.one_side": "Einseitig",
"ui.open": "Öffnen",
"ui.open_ellipsis": "Öffnen...",
"ui.open_preferences": "Einstellungen öffnen",
"ui.open_recent": "Zuletzt verwendet",
"ui.operation": "Operation",
"ui.options": "Optionen",
"ui.orbit_around": "Umkreisen um",
"ui.origin": "Ursprung",
"ui.palette_points": "Punkte",
"ui.palette_profile": "Profil",
"ui.pan_zoom_orbit": "Verschieben, zoomen, umkreisen",
"ui.parallel": "Parallel",
"ui.parameters": "Parameter",
"ui.paste": "Einfügen",
"ui.path": "Pfad",
"ui.pattern_on_path": "Pfadmuster",
"ui.perpendicular": "Senkrecht",
"ui.pipe": "Rohr",
"ui.placement": "Platzierung",
"ui.plane": "Ebene",
"ui.plane_at_angle": "Ebene im Winkel",
"ui.point": "Punkt",
"ui.point_under_the_cursor": "Punkt unter dem Cursor",
"ui.points": "Punkte",
"ui.polygon": "Polygon",
"ui.preferences": "Einstellungen",
"ui.profile": "Profil",
"ui.profile_plane": "Profilebene",
"ui.profiles": "Profile",
"ui.project": "Projizieren",
"ui.quantity_2": "Anzahl 2",
"ui.quantity": "Anzahl",
"ui.quit": "Beenden",
"ui.radius": "Radius",
"ui.radius_type": "Radius-Typ",
"ui.rectangle": "Rechteck",
"ui.rectangular_pattern": "Rechteckmuster",
"ui.redo": "Wiederholen",
"ui.render": "Rendern",
"ui.reset_the_settings_on_this_page": "Einstellungen auf dieser Seite zurücksetzen",
"ui.restore_defaults": "Standardeinstellungen wiederherstellen",
"ui.reverse_zoom_direction": "Zoomrichtung umkehren",
"ui.revolve": "Drehen",
"ui.right": "Rechts",
"ui.save": "Speichern",
"ui.save_as": "Speichern unter",
"ui.save_as_ellipsis": "Speichern unter...",
"ui.scale": "Skalieren",
"ui.section_analysis": "Querschnittsanalyse",
"ui.select": "Auswählen",
"ui.selection": "Auswahl",
"ui.shell": "Hülle",
"ui.show": "Einblenden",
"ui.show_constraints": "Bedingungen",
"ui.show_dimensions": "Maße",
"ui.show_points": "Punkte anzeigen",
"ui.show_profile": "Profil anzeigen",
"ui.show_projected_geometries": "Projizierte Geometrien",
"ui.simple": "Einfach",
"ui.simulation": "Simulation",
"ui.single": "Einzel",
"ui.sketch": "Skizze",
"ui.sketch_dimension": "Skizzenmaß",
"ui.sketch_grid": "Skizzenraster",
"ui.sketch_scale": "Skizzenskalierung",
"ui.sketch_status": "Skizzenstatus",
"ui.sketches": "Skizzen",
"ui.slice": "Scheibe",
"ui.slot": "Langloch",
"ui.smooth": "Glatt",
"ui.snap": "Einrasten",
"ui.spacing_2": "Abstand 2",
"ui.spacing": "Abstand",
"ui.sphere": "Sphäre",
"ui.spline": "Spline",
"ui.split_body": "Körper trennen",
"ui.start": "Start",
"ui.start_radius": "Startradius",
"ui.sweep": "Führen",
"ui.symmetric": "Symmetrisch",
"ui.symmetry": "Symmetrie",
"ui.system": "System",
"ui.tangent": "Tangential",
"ui.taper_angle": "Kegelwinkel",
"ui.tapped": "Gewindebohrung",
"ui.text": "Text",
"ui.theme": "Design",
"ui.thickness": "Dicke",
"ui.thread": "Gewinde",
"ui.timeline_bottom": "Timeline unten",
"ui.timeline_left_note": "Im linken Layout wird die Rücksetzmarke über dem Teile- und Szenenbrowser eingeblendet.",
"ui.timeline_left_sidebar": "Linke Seitenleiste",
"ui.tip_angle": "Spitzenwinkel",
"ui.to_object": "Bis Objekt",
"ui.top": "Oben",
"ui.torus": "Torus",
"ui.trim": "Kürzen",
"ui.two_sides": "Beidseitig",
"ui.ui_scale": "UI-Skalierung",
"ui.undo": "Rückgängig",
"ui.units": "Einheiten",
"ui.units_of_new_designs": "Einheiten für neue Designs",
"ui.untitled": "Ohne Titel",
"ui.variable": "Variabel",
"ui.view_centre": "Ansichtszentrum",
"ui.visual_style": "Ansichtsstil",
"ui.workspace": "Arbeitsbereich",
"ui.y_distance": "Y-Abstand",
"ui.x_distance": "X-Abstand",
"ui.zoom_to_the_cursor": "Zum Cursor zoomen",
"ui.animateJoint": "Gelenk animieren",
"ui.calibrate": "Kalibrieren",
"ui.customizeToolbar": "Symbolleiste anpassen…",
"ui.editJointLimits": "Gelenkgrenzen bearbeiten",
"ui.exportDxf": "Als DXF exportieren…",
"ui.findInTimeline": "In der Zeitleiste suchen",
"ui.findInWindow": "Im Fenster suchen",
"ui.fit": "Einpassen",
"ui.groupSelected": "Auswahl gruppieren",
"ui.hideAxes": "Achsen ausblenden",
"ui.hideDimension": "Bemaßung ausblenden",
"ui.hideOrigin": "Ursprung ausblenden",
"ui.hidePlanes": "Ebenen ausblenden",
"ui.hideProfile": "Profil ausblenden",
"ui.homeView": "Startansicht",
"ui.isolate": "Isolieren",
"ui.lock": "Sperren",
"ui.newSketchGroup": "Neue Skizzengruppe",
"ui.normalConstruction": "Normal / Konstruktion",
"ui.orbit": "Drehen",
"ui.heading_position": "POSITION",
"ui.pan": "Verschieben",
"ui.pinToShortcuts": "An Verknüpfungen anheften",
"ui.pinToToolbar": "An Symbolleiste anheften",
"ui.properties": "Eigenschaften",
"ui.removeSection": "Schnitt entfernen",
"ui.removeFromToolbar": "Aus der Symbolleiste entfernen",
"ui.repeat": "Wiederholen",
"ui.report_a_bug": "Fehler melden",
"ui.restore": "Wiederherstellen",
"ui.heading_setup": "EINRICHTEN",
"ui.selectOther": "Andere auswählen…",
"ui.selectTangentChain": "Tangentialkette auswählen",
"ui.showAll": "Alle einblenden",
"ui.showAllBodies": "Alle Volumenkörper einblenden",
"ui.showAxes": "Achsen einblenden",
"ui.showDimension": "Bemaßung einblenden",
"ui.showOrigin": "Ursprung einblenden",
"ui.showPlanes": "Ebenen einblenden",
"ui.suppress": "Unterdrücken",
"ui.command_palette_hint": "Befehl eingeben, z. B. {example} — oder S drücken, um zu suchen",
"ui.unground": "Fixierung lösen",
"ui.unisolate": "Isolierung aufheben",
"ui.unlock": "Entsperren",
"ui.unsuppress": "Unterdrückung aufheben",
"ui.updateToCurrentView": "Auf aktuelle Ansicht aktualisieren",
"ui.zoom": "Zoomen",
"ui.command_done": "{command} fertig",
"cmd.appearance_assign": "Erscheinungsbild",
"cmd.appearance_library": "Erscheinungsbild-Bibliothek",
"cmd.appearance_list": "Erscheinungsbilder auflisten",
"cmd.body_rename": "Körper umbenennen",
"cmd.browser_group": "Neue Gruppe",
"cmd.browser_move": "Browser-Elemente verschieben",
"cmd.browser_order": "Browser-Elemente ordnen",
"cmd.browser_rename_group": "Gruppe umbenennen",
"cmd.browser_ungroup": "Gruppierung aufheben",
"cmd.browser_visibility": "Ein-/Ausblenden",
"cmd.canvas_calibrate": "Zeichenfläche kalibrieren",
"cmd.canvas_decal": "Dekal",
"cmd.canvas_delete": "Zeichenfläche löschen",
"cmd.canvas_edit": "Zeichenfläche bearbeiten",
"cmd.canvas_insert": "Zeichenfläche",
"cmd.component_activate": "Komponente aktivieren",
"cmd.component_break_link": "Verknüpfung trennen",
"cmd.component_capture_position": "Position erfassen",
"cmd.component_create": "Neue Komponente",
"cmd.component_delete": "Komponente löschen",
"cmd.component_from_bodies": "Komponenten aus Körpern erstellen",
"cmd.component_insert_design": "Konstruktion einfügen",
"cmd.component_links": "Verknüpfungen auflisten",
"cmd.component_list": "Komponenten auflisten",
"cmd.component_move_bodies": "Körper in Komponente verschieben",
"cmd.component_move_sketches": "Skizzen in Komponente verschieben",
"cmd.component_paste_new": "Neu einfügen",
"cmd.component_properties": "Physikalische Eigenschaften der Komponente",
"cmd.component_rename": "Komponente umbenennen",
"cmd.component_revert_position": "Position zurücksetzen",
"cmd.component_update_links": "Verknüpfungen aktualisieren",
"cmd.config_activate": "Konfiguration aktivieren",
"cmd.config_column": "Konfigurationsspalte",
"cmd.config_configure": "Konfigurieren",
"cmd.config_preview": "Konfiguration vorschauen",
"cmd.config_row": "Konfigurationszeile",
"cmd.config_rule": "Konfigurationsregel",
"cmd.config_rules": "Konfigurationsregeln",
"cmd.config_table": "Konfigurationstabelle anzeigen",
"cmd.construct_axis_edge": "Achse durch Kante",
"cmd.construct_axis_normal_to_face": "Achse senkrecht zur Fläche",
"cmd.construct_axis_through_cylinder": "Achse durch Zylinder/Kegel/Torus",
"cmd.construct_axis_two_planes": "Achse durch zwei Ebenen",
"cmd.construct_axis_two_points": "Achse durch zwei Punkte",
"cmd.construct_plane_along_path": "Ebene entlang Pfad",
"cmd.construct_plane_midplane": "Mittenebene",
"cmd.construct_plane_perpendicular": "Rechtebene",
"cmd.construct_plane_tangent": "Tangentialebene",
"cmd.construct_plane_three_points": "Ebene durch drei Punkte",
"cmd.construct_plane_two_edges": "Ebene durch zwei Kanten",
"cmd.construct_point_along_path": "Punkt entlang Pfad",
"cmd.construct_point_center": "Punkt im Mittelpunkt von Kreis/Kugel/Torus",
"cmd.construct_point_edge_plane": "Punkt an Kante und Ebene",
"cmd.construct_point_three_planes": "Punkt aus drei Ebenen",
"cmd.construct_point_two_edges": "Punkt aus zwei Kanten",
"cmd.construct_point_vertex": "Punkt an Scheitelpunkt",
"cmd.contact_check": "Kontakt prüfen",
"cmd.contact_create": "Neuer Kontaktsatz",
"cmd.contact_disable_all": "Kontakt deaktivieren",
"cmd.contact_edit": "Kontaktsatz bearbeiten",
"cmd.contact_enable_all": "Alle Kontakte aktivieren",
"cmd.contact_enable_sets": "Kontaktsätze aktivieren",
"cmd.contact_list": "Kontaktsätze auflisten",
"cmd.diagnostics_crash_report": "Absturzbericht",
"cmd.diagnostics_crash_reports": "Absturzberichte",
"cmd.diagnostics_crash_reports_clear": "Absturzberichte löschen",
"cmd.diagnostics_crash_test": "Absturztest",
"cmd.doc_recover": "Konstruktion wiederherstellen",
"cmd.doc_recovery_discard": "Wiederhergestellte Konstruktion verwerfen",
"cmd.doc_recovery_list": "Wiederherstellbare Konstruktionen",
"cmd.document_inspect": "Konstruktion prüfen",
"cmd.document_units": "Einheiten der Konstruktion ändern",
"cmd.edit_checkpoint": "Prüfpunkt",
"cmd.edit_restore_checkpoint": "Prüfpunkt wiederherstellen",
"cmd.engine_commands": "Befehle auflisten",
"cmd.explode_create": "Explosionsdarstellung",
"cmd.explode_delete": "Explosionsdarstellung löschen",
"cmd.explode_list": "Explosionsdarstellungen auflisten",
"cmd.explode_show": "Explosionsdarstellung anzeigen",
"cmd.expr_evaluate": "Ausdruck auswerten",
"cmd.face_delete": "Fläche löschen",
"cmd.file_export": "Exportieren",
"cmd.file_insert_mesh": "Netz einfügen",
"cmd.file_insert_step": "STEP einfügen",
"cmd.file_save_mesh": "Als Netz speichern",
"cmd.inspect_accessibility": "Zugänglichkeitsanalyse",
"cmd.inspect_center_of_mass": "Massenschwerpunkt",
"cmd.inspect_curvature_comb": "Krümmungskamm-Analyse",
"cmd.inspect_curvature_map": "Krümmungskartenanalyse",
"cmd.inspect_draft": "Auswinkelanalyse",
"cmd.inspect_environment_map": "Umgebungskartenanalyse",
"cmd.inspect_interference": "Eingriff",
"cmd.inspect_isocurve": "Isokurvenanalyse",
"cmd.inspect_minimum_radius": "Mindestradiusanalyse",
"cmd.inspect_zebra": "Zebra-Analyse",
"cmd.joint_as_built": "Fertigungsgelenk",
"cmd.joint_create": "Gelenk",
"cmd.joint_delete": "Gelenk löschen",
"cmd.joint_drive": "Gelenke antreiben",
"cmd.joint_edit": "Gelenk bearbeiten",
"cmd.joint_limits": "Gelenkgrenzen",
"cmd.joint_list": "Gelenke auflisten",
"cmd.joint_motion_link": "Bewegungsverknüpfung",
"cmd.joint_origin": "Gelenkursprung",
"cmd.joint_rigid_group": "Starrgruppe",
"cmd.joint_solve": "Gelenke lösen",
"cmd.material_assign": "Physikalisches Material",
"cmd.material_create": "Neues physikalisches Material",
"cmd.material_delete": "Physisches Material löschen",
"cmd.material_edit": "Physisches Material bearbeiten",
"cmd.material_list": "Materialien auflisten",
"cmd.mesh_convert": "Netz konvertieren",
"cmd.model_edges": "Kanten auflisten",
"cmd.model_faces": "Flächen auflisten",
"cmd.model_threads": "Gewinde auflisten",
"cmd.motion_delete": "Bewegungsuntersuchung löschen",
"cmd.motion_export": "Bewegungsuntersuchung exportieren",
"cmd.motion_list": "Bewegungsuntersuchungen auflisten",
"cmd.motion_play": "Bewegungsuntersuchung abspielen",
"cmd.motion_study": "Bewegungsuntersuchung",
"cmd.occurrence_copy": "Einfügen (Instanz)",
"cmd.occurrence_ground": "Festsetzen",
"cmd.occurrence_move": "Instanz verschieben",
"cmd.occurrence_properties": "Physikalische Eigenschaften der Instanz",
"cmd.parameters_add": "Benutzerparameter hinzufügen",
"cmd.parameters_change": "Parameter ändern",
"cmd.parameters_comment": "Parameter kommentieren",
"cmd.parameters_complete": "Ausdruck vervollständigen",
"cmd.parameters_delete": "Parameter löschen",
"cmd.parameters_export": "Parameter exportieren",
"cmd.parameters_favorite": "Favorisierter Parameter",
"cmd.parameters_graph": "Parametergraph",
"cmd.parameters_import": "Parameter importieren",
"cmd.parameters_list": "Parameter auflisten",
"cmd.parameters_rename": "Parameter umbenennen",
"cmd.parameters_users": "Parameterverwendungen",
"cmd.parts_fastener": "Verbindungselement einfügen",
"cmd.parts_insert": "Bauteil einfügen",
"cmd.parts_library": "Standardbauteile",
"cmd.parts_resize": "Bauteilgröße ändern",
"cmd.plastic_assign_rule": "Kunststoffregel zuweisen",
"cmd.plastic_boss": "Erhöhung",
"cmd.plastic_lip": "Lippe",
"cmd.plastic_manage_rules": "Kunststoffregeln verwalten",
"cmd.plastic_rest": "Auflage",
"cmd.plastic_rules": "Kunststoffregeln auflisten",
"cmd.plastic_snap_fit": "Rastverbindung",
"cmd.select_clear": "Auswahl aufheben",
"cmd.sheet_convert": "In Blech umwandeln",
"cmd.sheet_export_bend_table": "Biegungstabelle exportieren",
"cmd.sheet_export_dxf": "Flachmuster als DXF exportieren",
"cmd.sheet_flange": "Flansch",
"cmd.sheet_flat_pattern": "Flachmuster erstellen",
"cmd.sheet_fold": "Falten",
"cmd.sheet_hem": "Umkantung",
"cmd.sheet_manage_rules": "Blechregeln",
"cmd.sheet_refold": "Wieder falten",
"cmd.sheet_rules": "Blechregeln auflisten",
"cmd.sheet_unfold": "Entfalten",
"cmd.sketch_arc_center_point": "Mittelpunkt-Kreisbogen",
"cmd.sketch_arc_tangent": "Tangentialbogen",
"cmd.sketch_arc_three_point": "3-Punkt-Kreisbogen",
"cmd.sketch_auto_constrain": "Auto-Bedingung",
"cmd.sketch_auto_constrain_from_datum": "Auto-Bedingung von Bezug",
"cmd.sketch_auto_project": "Kanten automatisch auf Referenz projizieren",
"cmd.sketch_blend_curve": "Übergangskurve",
"cmd.sketch_chain": "Kette",
"cmd.sketch_chamfer_distance_angle": "Abschrägung mit Abstand und Winkel",
"cmd.sketch_chamfer_equal_distance": "Abschrägung mit gleichem Abstand",
"cmd.sketch_chamfer_two_distance": "Abschrägung mit zwei Abständen",
"cmd.sketch_circle_center": "Kreis aus Mittelpunkt und Durchmesser",
"cmd.sketch_circle_three_point": "3-Punkt-Kreis",
"cmd.sketch_circle_three_tangent": "3-Tangenten-Kreis",
"cmd.sketch_circle_two_point": "2-Punkt-Kreis",
"cmd.sketch_circle_two_tangent": "2-Tangenten-Kreis",
"cmd.sketch_constrainer": "Skizzen-Bedingung",
"cmd.sketch_constraint_fix": "Fixieren/Lösen",
"cmd.sketch_constraint_midpoint": "Mittelpunkt",
"cmd.sketch_construction": "Normal/Konstruktion",
"cmd.sketch_delete": "Skizzenentitäten löschen",
"cmd.sketch_dimension_text": "Maßtext verschieben",
"cmd.sketch_drag": "Skizzengeometrie ziehen",
"cmd.sketch_drag_end": "Skizzen-Ziehen beenden",
"cmd.sketch_draw_dimensioned": "Skizzengeometrie mit Maßen zeichnen",
"cmd.sketch_edit": "Skizze bearbeiten",
"cmd.sketch_edit_text": "Text bearbeiten",
"cmd.sketch_export_dxf": "Als DXF speichern",
"cmd.sketch_finish_auto_constrain": "Mit Auto-Bedingung fertigstellen",
"cmd.sketch_fit_curves_to_section": "Kurven an Netzquerschnitt anpassen",
"cmd.sketch_glyphs": "Bedingungssymbole",
"cmd.sketch_include_3d": "3D-Geometrie einbeziehen",
"cmd.sketch_insert_dxf": "DXF einfügen",
"cmd.sketch_insert_svg": "SVG einfügen",
"cmd.sketch_inspect": "Skizze prüfen",
"cmd.sketch_intersection_curve": "Schnittkurve",
"cmd.sketch_isoparametric_curve": "Isoparametrische Kurve",
"cmd.sketch_line3d": "3D-Linie",
"cmd.sketch_line_midpoint": "Linie über den Mittelpunkt",
"cmd.sketch_move": "Skizzenentitäten verschieben/kopieren",
"cmd.sketch_move3d": "3D-Punkt verschieben",
"cmd.sketch_move_point": "Skizzenpunkt ziehen",
"cmd.sketch_offset_break": "Versatzverknüpfung trennen",
"cmd.sketch_options": "Skizzenpalettenoptionen",
"cmd.sketch_point3d": "3D-Punkt",
"cmd.sketch_polygon_circumscribed": "Umgeschriebenes Polygon",
"cmd.sketch_polygon_edge": "Polygon über eine Kante",
"cmd.sketch_polygon_inscribed": "Eingeschriebenes Polygon",
"cmd.sketch_project_to_surface": "Auf Fläche projizieren",
"cmd.sketch_rectangle_center": "Rechteck vom Mittelpunkt",
"cmd.sketch_rectangle_three_point": "3-Punkt-Rechteck",
"cmd.sketch_rectangle_two_point": "2-Punkt-Rechteck",
"cmd.sketch_redefine": "Skizzierebene neu definieren",
"cmd.sketch_slot_arc_center": "Mittelpunkt-Bogenschlitz",
"cmd.sketch_slot_arc_three_point": "3-Punkt-Bogenschlitz",
"cmd.sketch_slot_center_point": "Mittelpunktschlitz",
"cmd.sketch_slot_center_to_center": "Schlitz von Mittelpunkt zu Mittelpunkt",
"cmd.sketch_slot_overall": "Gesamtschlitz",
"cmd.sketch_snap": "Einrastinferenz",
"cmd.sketch_solve": "Skizze lösen",
"cmd.sketch_spline3d": "3D-Spline",
"cmd.sketch_spline_control_point": "Spline mit Kontrollpunkten",
"cmd.sketch_spline_control_point_5": "Spline mit Kontrollpunkten (Grad 5)",
"cmd.sketch_spline_fit_point": "Spline mit Fitpunkten",
"cmd.sketch_spun_profile": "Gedrehtes Profil",
"cmd.sketch_toggle_driven": "Abhängiges Maß umschalten",
"cmd.solid_align": "Ausrichten",
"cmd.solid_boundary_fill": "Begrenzungsfüllung",
"cmd.solid_bounding_solid": "Begrenzender Körper",
"cmd.solid_emboss": "Prägen",
"cmd.solid_move": "Verschieben/Kopieren",
"cmd.solid_offset_face": "Fläche versetzen",
"cmd.solid_press_pull": "Drücken/Ziehen",
"cmd.solid_remove": "Entfernen",
"cmd.solid_replace_face": "Fläche ersetzen",
"cmd.solid_rib": "Rippe",
"cmd.solid_split_face": "Fläche teilen",
"cmd.solid_web": "Gewebe",
"cmd.surface_patch": "Patch",
"cmd.surface_stitch": "Nähen",
"cmd.surface_thicken": "Dicke vergeben",
"cmd.surface_extrude": "Flächen-Extrusion",
"cmd.surface_revolve": "Flächen-Rotation",
"cmd.surface_sweep": "Flächen-Sweep",
"cmd.surface_loft": "Flächen-Loft",
"cmd.surface_ruled": "Regelfläche",
"cmd.surface_boundary": "Begrenzungsfläche",
"cmd.surface_offset": "Offsetfläche",
"cmd.surface_blend": "Verbindungsfläche",
"cmd.surface_fillet": "Flächenverrundung",
"cmd.surface_untrim": "Trimmen aufheben",
"cmd.surface_unstitch": "Verbund aufheben",
"cmd.surface_reverse_normal": "Normale umkehren",
"cmd.inspect_continuity": "Kontinuitätsprüfung",
"cmd.timeline_compute_all": "Alles berechnen",
"cmd.timeline_copy": "Merkmale kopieren",
"cmd.timeline_dependents": "Merkmalsabhängigkeiten",
"cmd.timeline_inspect": "Merkmal prüfen",
"cmd.timeline_paste": "Merkmale einfügen",
"cmd.timeline_redefine": "Merkmal bearbeiten",
"cmd.timeline_rename": "Umbenennen",
"cmd.timeline_reorder": "Merkmal neu ordnen",
"cmd.timeline_roll_to": "Verlaufsmarkierung verschieben",
"cmd.timeline_rollback": "Zeitleistenmarkierung verschieben",
"cmd.timeline_suppress": "Merkmal unterdrücken",
"cmd.view_apply": "Benannte Ansicht wiederherstellen",
"cmd.view_delete": "Benannte Ansicht löschen",
"cmd.view_list": "Benannte Ansichten auflisten",
"cmd.view_rename": "Benannte Ansicht umbenennen",
"cmd.view_save": "Neue benannte Ansicht",
"ui.export_ellipsis_keymap": "Exportieren…",
"ui.heading_configure": "KONFIGURIEREN",
"ui.heading_constraints": "RANDBEDINGUNGEN",
"ui.heading_utilities": "WERKZEUGE",
"BROWSER": "BROWSER",
"Drive Joint": "Gelenk antreiben",
"Edit Profile Sketch": "Profile-Skizze bearbeiten",
"Find in Browser": "Im Browser suchen",
"Report a bug — review a screenshot and open a GitHub draft": "Fehler melden — Screenshot prüfen und einen GitHub-Entwurf öffnen",
"Reset Toolbar": "Symbolleiste zurücksetzen",
"Select bodies (a face picks its body)": "Körper auswählen (eine Fläche wählt ihren Körper)",
"Selection filter": "Auswahlfilter",
"Revert Position": "Position zurücksetzen",
"Construction": "Konstruktionslinie",
"Faces": "Flächen",
"Edges": "Kanten",
"Vertices": "Scheitelpunkte",
"Sketch geometry and profiles": "Skizzengeometrie und Profile",
"Sketch curves": "Skizzenkurven",
"Sketch points": "Skizzenpunkte",
"Sketch dimensions": "Skizzenmaße",
"Sketch constraints": "Skizzenbedingungen",
"Flat": "Flach",
"cmd.render_backends": "Render-Backends",
"cmd.view_navigate": "Ansicht navigieren",
"timeline.group": "Neuen Merkmalsordner anlegen",
"timeline.ungroup": "Merkmalsordner entfernen",
"timeline.rename_group": "Merkmalsordner umbenennen",
"timeline.move_to_group": "Merkmale in Ordner verschieben",
"timeline.move_many": "Zeitleistenelemente verschieben",
"timeline.reorder_many": "Merkmale neu ordnen",
"ui.show_construction_geometries": "Konstruktionsgeometrien",
"ui.total_angle": "Gesamtwinkel",
"sketch_text.builtin_font": "{family} (integriert)",
"sketch_text.font_search": "Schriftarten suchen",
"sketch_text.loading_fonts": "Installierte Schriftarten werden gesucht…",
"sketch_text.use_font_file": "Schriftdatei {path} verwenden",
"sketch_text.bold": "Fett",
"sketch_text.italic": "Kursiv",
"sketch_text.align_left": "Links",
"sketch_text.align_center": "Mitte",
"sketch_text.align_right": "Rechts",
"sketch_text.path_hint": "Eine Skizzenkurve, der der Text folgt: hier klicken, dann die Kurve",
"sketch_text.path_clear": "Text von der Kurve lösen",
"sketch_text.on_path": "Der Text folgt seiner Kurve: sie bestimmt seine Lage"
}
+961
View File
@@ -0,0 +1,961 @@
{
"browser.body_name": "Body{number}",
"browser.component_name": "Component{number}",
"browser.parameters": "Parameters ({count})",
"browser.precision": "Precision: {precision}",
"browser.sketch_name": "Sketch{number}",
"browser.units": "Units: {units}",
"cmd.appearance_assign": "Appearance",
"cmd.appearance_library": "Appearance Library",
"cmd.appearance_list": "List Appearances",
"cmd.body_rename": "Rename Body",
"cmd.browser_group": "New Group",
"cmd.browser_move": "Move Browser Items",
"cmd.browser_order": "Order Browser Items",
"cmd.browser_rename_group": "Rename Group",
"cmd.browser_ungroup": "Ungroup",
"cmd.browser_visibility": "Show/Hide",
"cmd.canvas_calibrate": "Calibrate Canvas",
"cmd.canvas_decal": "Decal",
"cmd.canvas_delete": "Delete Canvas",
"cmd.canvas_edit": "Edit Canvas",
"cmd.canvas_insert": "Canvas",
"cmd.component_activate": "Activate Component",
"cmd.component_break_link": "Break Link",
"cmd.component_capture_position": "Capture Position",
"cmd.component_create": "New Component",
"cmd.component_delete": "Delete Component",
"cmd.component_from_bodies": "Create Components from Bodies",
"cmd.component_insert_design": "Insert Design",
"cmd.component_links": "List Links",
"cmd.component_list": "List Components",
"cmd.component_move_bodies": "Move Bodies to Component",
"cmd.component_move_sketches": "Move Sketches to Component",
"cmd.component_paste_new": "Paste New",
"cmd.component_properties": "Component Physical Properties",
"cmd.component_rename": "Rename Component",
"cmd.component_revert_position": "Revert Position",
"cmd.component_update_links": "Update Links",
"cmd.config_activate": "Activate Configuration",
"cmd.config_column": "Configuration Column",
"cmd.config_configure": "Configure",
"cmd.config_preview": "Preview Configuration",
"cmd.config_row": "Configuration Row",
"cmd.config_rule": "Configuration Rule",
"cmd.config_rules": "Configuration Rules",
"cmd.config_table": "Display Configuration Table",
"cmd.construct_axis_edge": "Axis Through Edge",
"cmd.construct_axis_normal_to_face": "Axis Perpendicular To Face",
"cmd.construct_axis_through_cylinder": "Axis Through Cylinder/Cone/Torus",
"cmd.construct_axis_two_planes": "Axis Through Two Planes",
"cmd.construct_axis_two_points": "Axis Through Two Points",
"cmd.construct_plane_along_path": "Plane Along Path",
"cmd.construct_plane_midplane": "Midplane",
"cmd.construct_plane_perpendicular": "Perpendicular Plane",
"cmd.construct_plane_tangent": "Tangent Plane",
"cmd.construct_plane_three_points": "Plane Through Three Points",
"cmd.construct_plane_two_edges": "Plane Through Two Edges",
"cmd.construct_point_along_path": "Point Along Path",
"cmd.construct_point_center": "Point At Center Of Circle/Sphere/Torus",
"cmd.construct_point_edge_plane": "Point At Edge And Plane",
"cmd.construct_point_three_planes": "Point Through Three Planes",
"cmd.construct_point_two_edges": "Point Through Two Edges",
"cmd.construct_point_vertex": "Point At Vertex",
"cmd.contact_check": "Check Contact",
"cmd.contact_create": "New Contact Set",
"cmd.contact_disable_all": "Disable Contact",
"cmd.contact_edit": "Edit Contact Set",
"cmd.contact_enable_all": "Enable All Contact",
"cmd.contact_enable_sets": "Enable Contact Sets",
"cmd.contact_list": "List Contact Sets",
"cmd.diagnostics_crash_report": "Crash Report",
"cmd.diagnostics_crash_reports": "Crash Reports",
"cmd.diagnostics_crash_reports_clear": "Delete Crash Reports",
"cmd.diagnostics_crash_test": "Crash Test",
"cmd.doc_recover": "Recover Design",
"cmd.doc_recovery_discard": "Discard Recovered Design",
"cmd.doc_recovery_list": "Recoverable Designs",
"cmd.document_inspect": "Inspect Design",
"cmd.document_units": "Change Design Units",
"cmd.edit_checkpoint": "Checkpoint",
"cmd.edit_restore_checkpoint": "Restore Checkpoint",
"cmd.engine_commands": "List Commands",
"cmd.explode_create": "Exploded View",
"cmd.explode_delete": "Delete Exploded View",
"cmd.explode_list": "List Exploded Views",
"cmd.explode_show": "Show Exploded View",
"cmd.expr_evaluate": "Evaluate Expression",
"cmd.face_delete": "Delete Face",
"cmd.file_export": "Export",
"cmd.file_insert_mesh": "Insert Mesh",
"cmd.file_insert_step": "Insert STEP",
"cmd.file_save_mesh": "Save As Mesh",
"cmd.inspect_accessibility": "Accessibility Analysis",
"cmd.inspect_center_of_mass": "Center of Mass",
"cmd.inspect_curvature_comb": "Curvature Comb Analysis",
"cmd.inspect_curvature_map": "Curvature Map Analysis",
"cmd.inspect_draft": "Draft Analysis",
"cmd.inspect_environment_map": "Environment Map Analysis",
"cmd.inspect_interference": "Interference",
"cmd.inspect_isocurve": "Isocurve Analysis",
"cmd.inspect_minimum_radius": "Minimum Radius Analysis",
"cmd.inspect_zebra": "Zebra Analysis",
"cmd.joint_as_built": "As-Built Joint",
"cmd.joint_create": "Joint",
"cmd.joint_delete": "Delete Joint",
"cmd.joint_drive": "Drive Joints",
"cmd.joint_edit": "Edit Joint",
"cmd.joint_limits": "Joint Limits",
"cmd.joint_list": "List Joints",
"cmd.joint_motion_link": "Motion Link",
"cmd.joint_origin": "Joint Origin",
"cmd.joint_rigid_group": "Rigid Group",
"cmd.joint_solve": "Solve Joints",
"cmd.material_assign": "Physical Material",
"cmd.material_create": "New Physical Material",
"cmd.material_delete": "Delete Physical Material",
"cmd.material_edit": "Edit Physical Material",
"cmd.material_list": "List Materials",
"cmd.mesh_convert": "Convert Mesh",
"cmd.model_edges": "List Edges",
"cmd.model_faces": "List Faces",
"cmd.model_threads": "List Threads",
"cmd.motion_delete": "Delete Motion Study",
"cmd.motion_export": "Export Motion Study",
"cmd.motion_list": "List Motion Studies",
"cmd.motion_play": "Play Motion Study",
"cmd.motion_study": "Motion Study",
"cmd.occurrence_copy": "Paste (Instance)",
"cmd.occurrence_ground": "Ground",
"cmd.occurrence_move": "Move Occurrence",
"cmd.occurrence_properties": "Occurrence Physical Properties",
"cmd.parameters_add": "Add User Parameter",
"cmd.parameters_change": "Change Parameters",
"cmd.parameters_comment": "Comment Parameter",
"cmd.parameters_complete": "Complete Expression",
"cmd.parameters_delete": "Delete Parameter",
"cmd.parameters_export": "Export Parameters",
"cmd.parameters_favorite": "Favourite Parameter",
"cmd.parameters_graph": "Parameter Graph",
"cmd.parameters_import": "Import Parameters",
"cmd.parameters_list": "List Parameters",
"cmd.parameters_rename": "Rename Parameter",
"cmd.parameters_users": "Parameter Users",
"cmd.parts_fastener": "Insert Fastener",
"cmd.parts_insert": "Insert Part",
"cmd.parts_library": "Standard Parts",
"cmd.parts_resize": "Change Part Size",
"cmd.plastic_assign_rule": "Assign Plastic Rule",
"cmd.plastic_boss": "Boss",
"cmd.plastic_lip": "Lip",
"cmd.plastic_manage_rules": "Manage Plastic Rules",
"cmd.plastic_rest": "Rest",
"cmd.plastic_rules": "List Plastic Rules",
"cmd.plastic_snap_fit": "Snap Fit",
"cmd.select_clear": "Clear Selection",
"cmd.sheet_convert": "Convert to Sheet Metal",
"cmd.sheet_export_bend_table": "Export Bend Table",
"cmd.sheet_export_dxf": "Export Flat Pattern as DXF",
"cmd.sheet_flange": "Flange",
"cmd.sheet_flat_pattern": "Create Flat Pattern",
"cmd.sheet_fold": "Fold",
"cmd.sheet_hem": "Hem",
"cmd.sheet_manage_rules": "Sheet Metal Rules",
"cmd.sheet_refold": "Refold",
"cmd.sheet_rules": "List Sheet Metal Rules",
"cmd.sheet_unfold": "Unfold",
"cmd.sketch_arc_center_point": "Center Point Arc",
"cmd.sketch_arc_tangent": "Tangent Arc",
"cmd.sketch_arc_three_point": "3-Point Arc",
"cmd.sketch_auto_constrain": "AutoConstrain",
"cmd.sketch_auto_constrain_from_datum": "AutoConstrain from datum",
"cmd.sketch_auto_project": "Auto-Project Edges on Reference",
"cmd.sketch_blend_curve": "Blend Curve",
"cmd.sketch_chain": "Chain",
"cmd.sketch_chamfer_distance_angle": "Distance and Angle Chamfer",
"cmd.sketch_chamfer_equal_distance": "Equal Distance Chamfer",
"cmd.sketch_chamfer_two_distance": "Two Distance Chamfer",
"cmd.sketch_circle_center": "Center Diameter Circle",
"cmd.sketch_circle_three_point": "3-Point Circle",
"cmd.sketch_circle_three_tangent": "3-Tangent Circle",
"cmd.sketch_circle_two_point": "2-Point Circle",
"cmd.sketch_circle_two_tangent": "2-Tangent Circle",
"cmd.sketch_constrainer": "Sketch Constrainer",
"cmd.sketch_constraint_fix": "Fix/UnFix",
"cmd.sketch_constraint_midpoint": "MidPoint",
"cmd.sketch_construction": "Normal/Construction",
"cmd.sketch_delete": "Delete Sketch Entities",
"cmd.sketch_dimension_text": "Move Dimension Text",
"cmd.sketch_drag": "Drag Sketch Geometry",
"cmd.sketch_drag_end": "End Sketch Drag",
"cmd.sketch_draw_dimensioned": "Draw Dimensioned Sketch Geometry",
"cmd.sketch_edit": "Edit Sketch",
"cmd.sketch_edit_text": "Edit Text",
"cmd.sketch_export_dxf": "Save As DXF",
"cmd.sketch_finish_auto_constrain": "Finish with AutoConstrain",
"cmd.sketch_fit_curves_to_section": "Fit Curves to Mesh Section",
"cmd.sketch_glyphs": "Constraint Glyphs",
"cmd.sketch_include_3d": "Include 3D Geometry",
"cmd.sketch_insert_dxf": "Insert DXF",
"cmd.sketch_insert_svg": "Insert SVG",
"cmd.sketch_inspect": "Inspect Sketch",
"cmd.sketch_intersection_curve": "Intersection Curve",
"cmd.sketch_isoparametric_curve": "Isoparametric Curve",
"cmd.sketch_line3d": "3D Line",
"cmd.sketch_line_midpoint": "Midpoint Line",
"cmd.sketch_move": "Move/Copy Sketch Entities",
"cmd.sketch_move3d": "Move 3D Point",
"cmd.sketch_move_point": "Drag Sketch Point",
"cmd.sketch_offset_break": "Break Offset Association",
"cmd.sketch_options": "Sketch Palette Options",
"cmd.sketch_point3d": "3D Point",
"cmd.sketch_polygon_circumscribed": "Circumscribed Polygon",
"cmd.sketch_polygon_edge": "Edge Polygon",
"cmd.sketch_polygon_inscribed": "Inscribed Polygon",
"cmd.sketch_project_to_surface": "Project To Surface",
"cmd.sketch_rectangle_center": "Center Rectangle",
"cmd.sketch_rectangle_rounded": "Rounded Rectangle",
"cmd.sketch_rectangle_three_point": "3-Point Rectangle",
"cmd.sketch_rectangle_two_point": "2-Point Rectangle",
"cmd.sketch_redefine": "Redefine Sketch Plane",
"cmd.sketch_slot_arc_center": "Center Point Arc Slot",
"cmd.sketch_slot_arc_three_point": "Three Point Arc Slot",
"cmd.sketch_slot_center_point": "Center Point Slot",
"cmd.sketch_slot_center_to_center": "Center to Center Slot",
"cmd.sketch_slot_overall": "Overall Slot",
"cmd.sketch_snap": "Snap Inference",
"cmd.sketch_solve": "Solve Sketch",
"cmd.sketch_spline3d": "3D Spline",
"cmd.sketch_spline_control_point": "Control Point Spline",
"cmd.sketch_spline_control_point_5": "Control Point Spline (Degree 5)",
"cmd.sketch_spline_fit_point": "Fit Point Spline",
"cmd.sketch_spun_profile": "Spun Profile",
"cmd.sketch_toggle_driven": "Toggle Driven Dimension",
"cmd.solid_align": "Align",
"cmd.solid_boundary_fill": "Boundary Fill",
"cmd.solid_bounding_solid": "Bounding Solid",
"cmd.solid_emboss": "Emboss",
"cmd.solid_move": "Move/Copy",
"cmd.solid_offset_face": "Offset Face",
"cmd.solid_press_pull": "Press Pull",
"cmd.solid_remove": "Remove",
"cmd.solid_replace_face": "Replace Face",
"cmd.solid_rib": "Rib",
"cmd.solid_split_face": "Split Face",
"cmd.solid_web": "Web",
"cmd.surface_patch": "Patch",
"cmd.surface_stitch": "Stitch",
"cmd.surface_thicken": "Thicken",
"cmd.surface_extrude": "Surface Extrude",
"cmd.surface_revolve": "Surface Revolve",
"cmd.surface_sweep": "Surface Sweep",
"cmd.surface_loft": "Surface Loft",
"cmd.surface_ruled": "Ruled Surface",
"cmd.surface_boundary": "Boundary Surface",
"cmd.surface_offset": "Offset Surface",
"cmd.surface_blend": "Blend Surface",
"cmd.surface_fillet": "Surface Fillet",
"cmd.surface_untrim": "Untrim",
"cmd.surface_unstitch": "Unstitch",
"cmd.surface_reverse_normal": "Reverse Normal",
"cmd.inspect_continuity": "Continuity Analysis",
"cmd.timeline_compute_all": "Compute All",
"cmd.timeline_copy": "Copy Features",
"cmd.timeline_dependents": "Feature Dependents",
"cmd.timeline_inspect": "Inspect Feature",
"cmd.timeline_paste": "Paste Features",
"cmd.timeline_redefine": "Edit Feature",
"cmd.timeline_rename": "Rename",
"cmd.timeline_reorder": "Reorder Feature",
"cmd.timeline_roll_to": "Roll History Marker",
"cmd.timeline_rollback": "Move Timeline Marker",
"cmd.timeline_suppress": "Suppress Feature",
"cmd.view_apply": "Restore Named View",
"cmd.view_delete": "Delete Named View",
"cmd.view_list": "List Named Views",
"cmd.view_rename": "Rename Named View",
"cmd.view_save": "New Named View",
"dialog.custom_axis": "↖ Custom Axis",
"dialog.edit_title": "EDIT {title}",
"dialog.select": "↖ Select",
"dialog.selected_count": "↖ {count} selected",
"dialog.step": "Step",
"dialog.selection_step": "{step} of {total}: Select {input}",
"dialog.selection_guidance": "Select an input to change it. Click selected objects to remove them; Ctrl/Cmd-click also toggles them. Review the preview, then press OK.",
"document.save_changes": "Save changes to {name}?",
"document.unsaved_changes": "{name} has unsaved changes. If you don't save, they are lost.",
"settings.msaa_applies_on_restart": "Anti-aliasing applies the next time SolidCraft starts",
"settings.msaa_reset": "{setting}: {requested}× isn't supported by this graphics device; it is now {reset}× (supported: {supported}).",
"settings.msaa_unsupported": "Not supported by this graphics device",
"settings.reset_intro": "These settings don't work on this computer and would keep SolidCraft from starting, so they were changed:",
"settings.reset_title": "Settings were changed",
"sketch.degrees_of_freedom": "{count} degrees of freedom",
"sketch.draw_curves": "Draw as {linetype}",
"sketch.make_selection": "Convert the selection to {linetype}",
"ui.1_degree_of_freedom": "1 degree of freedom",
"ui.3d_mouse_speed": "3D mouse speed",
"ui.3d_mouse_windows": "3D mouse (Windows)",
"ui.3d_sketch": "3D Sketch",
"ui.about": "About",
"ui.activate": "Activate",
"ui.active_component": "Active component",
"ui.agent_cursor": "Agent cursor",
"ui.agent_cursor_speed": "Agent cursor speed",
"ui.all": "All",
"ui.analysis": "Analysis",
"ui.angle": "Angle",
"ui.angle_decimals": "Angle decimals",
"ui.animate_view_changes": "Animate view changes",
"ui.animation": "Animation",
"ui.animation_speed": "Animation speed",
"ui.anti_aliasing": "Anti-aliasing",
"ui.apply": "Apply",
"ui.arc": "Arc",
"ui.assemble": "Assemble",
"ui.autosave_every": "Autosave every",
"ui.axes": "Axes",
"ui.axis": "Axis",
"ui.base_point": "Base point",
"ui.bodies": "Bodies",
"ui.box": "Box",
"ui.break": "Break",
"ui.cancel": "Cancel",
"ui.canvases": "Canvases",
"ui.center": "Center",
"ui.centerline": "Centerline",
"ui.chain": "Chain",
"ui.chamfer": "Chamfer",
"ui.chamfer_type": "Chamfer Type",
"ui.change_workspace": "Change workspace",
"ui.chord_length": "Chord Length",
"ui.curvature_g2": "Curvature (G2)",
"ui.equivalent_radius": "Equivalent Radius",
"ui.circle": "Circle",
"ui.circular_pattern": "Circular Pattern",
"ui.clear": "Clear",
"ui.clear_recent": "Clear Recent",
"ui.clear_the_selection": "Clear the selection",
"ui.click_selects_whole_bodies": "Click selects whole bodies",
"ui.click_then_press_key": "Click, then press the new key",
"ui.close": "Close",
"ui.coil": "Coil",
"ui.coincident": "Coincident",
"ui.collapse": "Collapse",
"ui.collinear": "Collinear",
"ui.combine": "Combine",
"ui.coming_later": "coming later",
"ui.concentric": "Concentric",
"ui.configurations": "Configurations",
"ui.conic_curve": "Conic Curve",
"ui.constant": "Constant",
"ui.constraints_conflict": "Constraints conflict",
"ui.construct": "Construct",
"ui.construction": "Construction",
"ui.context_menu": "Context menu",
"ui.copy": "Copy",
"ui.corner": "Corner",
"ui.corners": "Corners",
"ui.counterbore": "Counterbore",
"ui.countersink": "Countersink",
"ui.create": "Create",
"ui.create_copy": "Create Copy",
"ui.create_sketch": "Create Sketch",
"ui.current": "current",
"ui.curvature": "Curvature",
"ui.curves": "Curves",
"ui.cut": "Cut",
"ui.cylinder": "Cylinder",
"ui.dark": "Dark",
"ui.default_modelling_orientation": "Default modelling orientation",
"ui.degrees_of_freedom": "degrees of freedom",
"ui.delete": "Delete",
"ui.depth": "Depth",
"ui.design": "Design",
"ui.diameter": "Diameter",
"ui.dimension": "Dimension",
"ui.direction": "Direction",
"ui.distance": "Distance",
"ui.distance_1": "Distance 1",
"ui.distance_2": "Distance 2",
"ui.document_settings": "Document Settings",
"ui.don_t_save": "Don't Save",
"ui.draft": "Draft",
"ui.drawing": "Drawing",
"ui.drill_point": "Drill Point",
"ui.edit": "Edit",
"ui.edit_section_analysis": "Edit Section Analysis",
"ui.ellipse": "Ellipse",
"ui.end_radius": "End Radius",
"ui.entities": "Entities",
"ui.equal": "Equal",
"ui.expand": "Expand",
"ui.export_ellipsis": "Export...",
"ui.export_ellipsis_keymap": "Export…",
"ui.export_keymap_tip": "Keys, S box favourites and the toolbar, as a file",
"ui.extend": "Extend",
"ui.extent_type": "Extent Type",
"ui.extrude": "Extrude",
"ui.factor": "Factor",
"ui.feature_history": "Feature history",
"ui.feature_options": "Feature Options",
"ui.features": "Features",
"ui.file": "File",
"ui.fillet": "Fillet",
"ui.filter_commands_or_keys": "Filter commands or keys",
"ui.finish_sketch": "Finish Sketch",
"ui.fix_unfix": "Fix/Unfix",
"ui.flat": "Flat",
"ui.flip": "Flip",
"ui.front": "Front",
"ui.fully_constrained": "Fully constrained",
"ui.general": "General",
"ui.graphics": "Graphics",
"ui.ground_shadow": "Ground shadow",
"ui.heading_add_ins": "ADD-INS",
"ui.heading_assemble": "ASSEMBLE",
"ui.heading_box": "BOX",
"ui.heading_chamfer": "CHAMFER",
"ui.heading_circular_pattern": "CIRCULAR PATTERN",
"ui.heading_coil": "COIL",
"ui.heading_combine": "COMBINE",
"ui.heading_configure": "CONFIGURE",
"ui.heading_constraints": "CONSTRAINTS",
"ui.heading_construct": "CONSTRUCT",
"ui.heading_create": "CREATE",
"ui.heading_create_sketch": "CREATE SKETCH",
"ui.heading_cylinder": "CYLINDER",
"ui.heading_design": "DESIGN",
"ui.heading_draft": "DRAFT",
"ui.heading_edit": "EDIT",
"ui.heading_extrude": "EXTRUDE",
"ui.heading_fillet": "FILLET",
"ui.heading_finish_sketch": "FINISH SKETCH",
"ui.heading_front": "FRONT",
"ui.heading_hole": "HOLE",
"ui.heading_home": "HOME",
"ui.heading_insert": "INSERT",
"ui.heading_inspect": "INSPECT",
"ui.heading_loft": "LOFT",
"ui.heading_make": "MAKE",
"ui.heading_measure": "MEASURE",
"ui.heading_mesh": "MESH",
"ui.heading_mirror": "MIRROR",
"ui.heading_modify": "MODIFY",
"ui.heading_move": "MOVE",
"ui.heading_offset": "OFFSET",
"ui.heading_move_copy": "MOVE/COPY",
"ui.heading_offset_plane": "OFFSET PLANE",
"ui.heading_ok": "OK",
"ui.heading_parameters": "PARAMETERS",
"ui.heading_pattern_on_path": "PATTERN ON PATH",
"ui.heading_pipe": "PIPE",
"ui.heading_plane_at_angle": "PLANE AT ANGLE",
"ui.heading_plastic": "PLASTIC",
"ui.heading_prepare": "PREPARE",
"ui.heading_rectangular_pattern": "RECTANGULAR PATTERN",
"ui.heading_revolve": "REVOLVE",
"ui.heading_right": "RIGHT",
"ui.heading_scale": "SCALE",
"ui.heading_section_analysis": "SECTION ANALYSIS",
"ui.heading_select": "SELECT",
"ui.heading_sheet_metal": "SHEET METAL",
"ui.heading_shell": "SHELL",
"ui.heading_sketch": "SKETCH",
"ui.heading_sketch_palette": "SKETCH PALETTE",
"ui.heading_sketch_scale": "SKETCH SCALE",
"ui.heading_solid": "SOLID",
"ui.heading_sphere": "SPHERE",
"ui.heading_split_body": "SPLIT BODY",
"ui.heading_surface": "SURFACE",
"ui.heading_sweep": "SWEEP",
"ui.heading_thread": "THREAD",
"ui.heading_top": "TOP",
"ui.heading_torus": "TORUS",
"ui.heading_utilities": "UTILITIES",
"ui.heading_utility": "UTILITY",
"ui.help": "Help",
"ui.hide": "Hide",
"ui.hole": "Hole",
"ui.hole_tap_type": "Hole Tap Type",
"ui.hole_type": "Hole Type",
"ui.home": "Home",
"ui.horizontal_vertical": "Horizontal/Vertical",
"ui.import_ellipsis": "Import…",
"ui.insert": "Insert",
"ui.inspect": "Inspect",
"ui.intersect": "Intersect",
"ui.join": "Join",
"ui.joints": "Joints",
"ui.keyboard_shortcuts": "Keyboard Shortcuts",
"ui.language": "Language",
"ui.length_decimals": "Length decimals",
"ui.light": "Light",
"ui.line": "Line",
"ui.linetype": "Linetype",
"ui.loft": "Loft",
"ui.look_at": "Look At",
"ui.look_at_anim_desc": "Axis view, Look At, Home and Fit",
"ui.manufacture": "Manufacture",
"ui.measure": "Measure",
"ui.midpoint": "Midpoint",
"ui.mirror": "Mirror",
"ui.mirror_line": "Mirror line",
"ui.modify": "Modify",
"ui.move": "Move",
"ui.multiple": "Multiple",
"ui.named_views": "Named Views",
"ui.navigation": "Navigation",
"ui.needs_design": "{command}: open a design first",
"ui.new_body": "New Body",
"ui.new_design": "New Design",
"ui.normal": "Normal",
"ui.object_type": "Object Type",
"ui.objects": "Objects",
"ui.offer_to_stand_up_y_up_files": "Offer to stand up Y-up files",
"ui.offset": "Offset",
"ui.offset_plane": "Offset Plane",
"ui.one_side": "One Side",
"ui.open": "Open",
"ui.open_ellipsis": "Open...",
"ui.open_preferences": "Open Preferences",
"ui.open_recent": "Open Recent",
"ui.operation": "Operation",
"ui.options": "Options",
"ui.orbit_around": "Orbit around",
"ui.origin": "Origin",
"ui.palette_points": "Points",
"ui.palette_profile": "Profile",
"ui.pan_zoom_orbit": "Pan, zoom, orbit",
"ui.parallel": "Parallel",
"ui.parameters": "Parameters",
"ui.paste": "Paste",
"ui.path": "Path",
"ui.pattern_on_path": "Pattern on Path",
"ui.perpendicular": "Perpendicular",
"ui.perspective_view": "Perspective view",
"ui.pipe": "Pipe",
"ui.placement": "Placement",
"ui.plane": "Plane",
"ui.plane_at_angle": "Plane at Angle",
"ui.point": "Point",
"ui.point_under_the_cursor": "Point under the cursor",
"ui.points": "Points",
"ui.polygon": "Polygon",
"ui.preferences": "Preferences",
"ui.press_a_key": "press a key…",
"ui.profile": "Profile",
"ui.profile_plane": "Profile Plane",
"ui.profiles": "Profiles",
"ui.project": "Project",
"ui.project_face_edges_sketch": "Project face edges into new sketches",
"ui.quantity": "Quantity",
"ui.quantity_2": "Quantity 2",
"ui.quit": "Quit",
"ui.radial_context_menu": "Radial context menu",
"ui.radius": "Radius",
"ui.radius_type": "Radius Type",
"ui.rectangle": "Rectangle",
"ui.rectangular_pattern": "Rectangular Pattern",
"ui.redo": "Redo",
"ui.render": "Render",
"ui.renderer": "Renderer",
"ui.reset": "Reset",
"ui.reset_all": "Reset All",
"ui.reset_the_settings_on_this_page": "Reset the settings on this page",
"ui.restore_defaults": "Restore Defaults",
"ui.reverse_zoom_direction": "Reverse zoom direction",
"ui.revolve": "Revolve",
"ui.right": "Right",
"ui.save": "Save",
"ui.save_as": "Save As",
"ui.save_as_ellipsis": "Save As...",
"ui.scale": "Scale",
"ui.section_analysis": "Section Analysis",
"ui.select": "Select",
"ui.select_other_hint": "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.",
"ui.selection": "Selection",
"ui.shell": "Shell",
"ui.show": "Show",
"ui.show_constraints": "Constraints",
"ui.show_dimensions": "Dimensions",
"ui.show_points": "Show Points",
"ui.show_profile": "Show Profile",
"ui.show_projected_geometries": "Projected Geometries",
"ui.simple": "Simple",
"ui.simulation": "Simulation",
"ui.single": "Single",
"ui.sketch": "Sketch",
"ui.sketch_dimension": "Sketch Dimension",
"ui.sketch_grid": "Sketch Grid",
"ui.sketch_scale": "Sketch Scale",
"ui.sketch_status": "Sketch Status",
"ui.sketches": "Sketches",
"ui.slice": "Slice",
"ui.slot": "Slot",
"ui.smooth": "Smooth",
"ui.smooth_wheel_zoom": "Smooth wheel zoom",
"ui.smooth_wheel_zoom_tip": "Turn off to apply each wheel event immediately, without a smoothing tail",
"ui.snap": "Snap",
"ui.space_mouse_hint": "Move and rotate the view together; sketches use pan and zoom only.",
"ui.space_mouse_navigate": "Navigate with SpaceNavigator / SpaceMouse",
"ui.spacing": "Spacing",
"ui.spacing_2": "Spacing 2",
"ui.sphere": "Sphere",
"ui.spline": "Spline",
"ui.split_body": "Split Body",
"ui.stand_up_tip": "After a STEP or IGES file opens with Z up, offer a quarter turn about X",
"ui.start": "Start",
"ui.start_radius": "Start Radius",
"ui.sweep": "Sweep",
"ui.symmetric": "Symmetric",
"ui.symmetry": "Symmetry",
"ui.system": "System",
"ui.tangent": "Tangent",
"ui.taper_angle": "Taper Angle",
"ui.tapped": "Tapped",
"ui.text": "Text",
"ui.theme": "Theme",
"ui.thickness": "Thickness",
"ui.thread": "Thread",
"ui.timeline_bottom": "Bottom timeline",
"ui.timeline_left_note": "The left layout shows the rollback bar above the parts and scene browser.",
"ui.timeline_left_sidebar": "Left sidebar",
"ui.tip_angle": "Tip Angle",
"ui.to_object": "To Object",
"ui.top": "Top",
"ui.torus": "Torus",
"ui.trackpad_pan_tip": "Trackpad gestures pan (off: they zoom like the wheel); mouse wheel zooms, thumb wheel pans sideways, pinch always zooms",
"ui.trim": "Trim",
"ui.two_finger_scroll_pans": "Two-finger scroll pans",
"ui.two_sides": "Two Sides",
"ui.ui_scale": "UI scale",
"ui.undo": "Undo",
"ui.units": "Units",
"ui.units_of_new_designs": "Units of new designs",
"ui.untitled": "Untitled",
"ui.up_axis_note": "Which axis points up in the views (axis view, Home, Look At). Designs are unchanged.",
"ui.variable": "Variable",
"ui.view_centre": "View centre",
"ui.visual_shaded_hidden_edges": "Shaded with hidden edges",
"ui.visual_shaded_with_edges": "Shaded with edges",
"ui.visual_style": "Visual style",
"ui.visual_wireframe": "Wireframe",
"ui.workspace": "workspace",
"ui.x_distance": "X Distance",
"ui.y_distance": "Y Distance",
"ui.y_up": "Y up",
"ui.z_up": "Z up",
"ui.zoom_to_cursor_tip": "The wheel zooms toward the point under the cursor (off: toward the view's centre)",
"ui.zoom_to_the_cursor": "Zoom to the cursor",
"A new configuration, a copy of the active one": "A new configuration, a copy of the active one",
"About SolidCraft": "About SolidCraft",
"Active": "Active",
"Active rule": "Active rule",
"Add": "Add",
"Add Key": "Add Key",
"Add Mid Radius": "Add Mid Radius",
"Add Row": "Add Row",
"All Sketch Points": "All Sketch Points",
"All open designs": "All open designs",
"Allowed expressions, separated by semicolons": "Allowed expressions, separated by semicolons",
"Also deletes": "Also deletes",
"Angle 2": "Angle 2",
"Animate": "Animate",
"Appearance name": "Appearance name",
"Applied Radius": "Applied Radius",
"Apply To": "Apply To",
"Area": "Area",
"Attachment": "Attachment",
"BROWSER": "BROWSER",
"Bend Line Position": "Bend Line Position",
"Bend Position": "Bend Position",
"Bend Radius": "Bend Radius",
"Bends": "Bends",
"Body": "Body",
"Bounding box": "Bounding box",
"CSV or JSON: updates existing parameters, adds new ones": "CSV or JSON: updates existing parameters, adds new ones",
"Capture All Positions": "Capture All Positions",
"Catch Direction": "Catch Direction",
"Cells": "Cells",
"Center X": "Center X",
"Center Y": "Center Y",
"Center of mass": "Center of mass",
"Chamfer Type": "Chamfer Type",
"Check collisions": "Check collisions",
"Checked": "Checked",
"Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.",
"Close draft": "Close draft",
"Collisions": "Collisions",
"Colour": "Colour",
"Component1": "Component1",
"Configuration": "Configuration",
"Configuration rules": "Configuration rules",
"Contact": "Contact",
"Copy all licences": "Copy all licences",
"Copy diagnostic information": "Copy diagnostic information",
"Copy report": "Copy report",
"Corner Relief Shape": "Corner Relief Shape",
"Counterbore depth": "Counterbore depth",
"Countersink angle": "Countersink angle",
"Create Issue — review draft": "Create Issue — review draft",
"Customize Toolbar": "Customize Toolbar",
"Delete all": "Delete all",
"Delete rule": "Delete rule",
"Delete the row": "Delete the row",
"Delete the set": "Delete the set",
"Delete this parameter": "Delete this parameter",
"Density": "Density",
"Designs you open or save appear here.": "Designs you open or save appear here.",
"Determined by To Object. Choose Angle to edit the angular extent.": "Determined by To Object. Choose Angle to edit the angular extent.",
"Discard": "Discard",
"Double-click to rename": "Double-click to rename",
"Drive Joint": "Drive Joint",
"Edit Profile Sketch": "Edit Profile Sketch",
"Enable rule": "Enable rule",
"Enabled": "Enabled",
"Explode": "Explode",
"Extents": "Extents",
"Face attachment lost": "Face attachment lost",
"Faces / edges / vertices": "Faces / edges / vertices",
"Favourite": "Favourite",
"Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "Files from many CAD programs have Y up, so they open lying on their back here (Z up).",
"Filter": "Filter",
"Find in Browser": "Find in Browser",
"Flat size": "Flat size",
"Follow camera (Look At sketches)": "Follow camera (Look At sketches)",
"Font": "Font",
"Full Length": "Full Length",
"Gap": "Gap",
"Grid": "Grid",
"Height": "Height",
"Home view": "Home view",
"In This Design": "In This Design",
"Interferes": "Interferes",
"Issue title": "Issue title",
"Joint Origin1": "Joint Origin1",
"Keep Tools": "Keep Tools",
"Later": "Later",
"Leave it": "Leave it",
"Legacy face attachment. Rebinding to a certified face creates an exact attachment.": "Legacy face attachment. Rebinding to a certified face creates an exact attachment.",
"Length": "Length",
"Length units": "Length units",
"Library": "Library",
"Loop": "Loop",
"Margin": "Margin",
"Mass": "Mass",
"Material": "Material",
"Mid Radius": "Mid Radius",
"Modeled": "Modeled",
"More toolbar panels": "More toolbar panels",
"Motion": "Motion",
"Move Type": "Move Type",
"Move earlier": "Move earlier",
"Move later": "Move later",
"Name": "Name",
"No crash reports.": "No crash reports.",
"No matching appearances": "No matching appearances",
"Not available yet": "Not available yet",
"Opacity": "Opacity",
"Open GitHub draft": "Open GitHub draft",
"Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.",
"Other side": "Other side",
"Parameter values": "Parameter values",
"Per Edge": "Per Edge",
"Pick the tool bodies": "Pick the tool bodies",
"Place at the origin instead": "Place at the origin instead",
"Playback": "Playback",
"Position": "Position",
"Position (0 to 1)": "Position (0 to 1)",
"Position along the edge, 0 at the start end, 1 at the other": "Position along the edge, 0 at the start end, 1 at the other",
"Preview leaves the current design unchanged.": "Preview leaves the current design unchanged.",
"RECENT": "RECENT",
"Radius Fit": "Radius Fit",
"Ratio": "Ratio",
"Recover": "Recover",
"Recover unsaved design?": "Recover unsaved design?",
"Remove Tools": "Remove Tools",
"Remove from list": "Remove from list",
"Remove private design details or paths before sharing.": "Remove private design details or paths before sharing.",
"Remove the column": "Remove the column",
"Remove the track": "Remove the track",
"Remove this point": "Remove this point",
"Report a bug — review a screenshot and open a GitHub draft": "Report a bug — review a screenshot and open a GitHub draft",
"Reset Toolbar": "Reset Toolbar",
"Restore the inherited component or physical material appearance": "Restore the inherited component or physical material appearance",
"Result": "Result",
"Retake screenshot": "Retake screenshot",
"Reverse": "Reverse",
"Reverse Direction": "Reverse Direction",
"Revert": "Revert",
"Revert All Positions": "Revert All Positions",
"Review the report and screenshot before opening GitHub.": "Review the report and screenshot before opening GitHub.",
"Rim Edge": "Rim Edge",
"Roll History Marker Here": "Roll History Marker Here",
"Rule": "Rule",
"Saved preview. Geometry is being checked before editing.": "Saved preview. Geometry is being checked before editing.",
"Scale factor": "Scale factor",
"Search": "Search",
"Search commands": "Search commands",
"Select bodies (a face picks its body)": "Select bodies (a face picks its body)",
"Select bodies or faces instead": "Select bodies or faces instead",
"Selection filter": "Selection filter",
"Semantic faces share their appearance across instances.": "Semantic faces share their appearance across instances.",
"Shape": "Shape",
"Show Agent Cursor": "Show Agent Cursor",
"Show in folder": "Show in folder",
"Size": "Size",
"Sketch plane has no body-face attachment.": "Sketch plane has no body-face attachment.",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft closed before these designs were saved. Their last autosave can be opened.",
"Speed": "Speed",
"Stand it up": "Stand it up",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "Stand it up: a quarter turn about X, as one Move feature you can undo.",
"Stand this part up?": "Stand this part up?",
"Step": "Step",
"Steps": "Steps",
"Suppressed": "Suppressed",
"Swap Ends": "Swap Ends",
"Tangent Chain": "Tangent Chain",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "The dashed sketch is a saved plane guide. Select a new face to repair the attachment.",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "The saved author occurrence is missing. Select a valid author placement before rebinding.",
"Then": "Then",
"These features will be deleted:": "These features will be deleted:",
"These features will fail:": "These features will fail:",
"This child is shared. Deleting it removes it from every copy of its parent.": "This child is shared. Deleting it removes it from every copy of its parent.",
"This child is shared. Position changes apply to every copy of its parent.": "This child is shared. Position changes apply to every copy of its parent.",
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.",
"To Offset": "To Offset",
"Tolerance": "Tolerance",
"Type": "Type",
"Updating the preview": "Updating the preview",
"Use fixed plane": "Use fixed plane",
"VIEW": "VIEW",
"Value": "Value",
"Volume": "Volume",
"Wall": "Wall",
"When": "When",
"Width": "Width",
"Will fail": "Will fail",
"angle, height": "angle, height",
"command name": "command name",
"expression, e.g. 25 mm": "expression, e.g. 25 mm",
"fits the face, e.g. M8": "fits the face, e.g. M8",
"from the rule": "from the rule",
"needs two joints": "needs two joints",
"new configuration": "new configuration",
"new name": "new name",
"new parameter": "new parameter",
"no interference": "no interference",
"no joint that moves yet": "no joint that moves yet",
"no picture": "no picture",
"pick a hole to seat it, or OK places it at the origin": "pick a hole to seat it, or OK places it at the origin",
"pick one or two vertices, edges or faces": "pick one or two vertices, edges or faces",
"the parts library is not available": "the parts library is not available",
"to the next face": "to the next face",
"Revert Position": "Revert Position",
"Construction": "Construction",
"Faces": "Faces",
"Edges": "Edges",
"Vertices": "Vertices",
"Sketch geometry and profiles": "Sketch geometry and profiles",
"Sketch curves": "Sketch curves",
"Sketch points": "Sketch points",
"Sketch dimensions": "Sketch dimensions",
"Sketch constraints": "Sketch constraints",
"0 turns it off": "0 turns it off",
"Show the pointer an AI agent moves (#35)": "Show the pointer an AI agent moves (#35)",
"Follow the camera (Look At sketches)": "Follow the camera (Look At sketches)",
"Flat": "Flat",
"Save Rule": "Save Rule",
"Preview Row": "Preview Row",
"Add condition": "Add condition",
"Apply to selection": "Apply to selection",
"Remove override": "Remove override",
"Copy screenshot": "Copy screenshot",
"Remove screenshot": "Remove screenshot",
"Use active author placement": "Use active author placement",
"Reattach selected face": "Reattach selected face",
"Use saved fixed plane": "Use saved fixed plane",
"Use selected face as fixed plane": "Use selected face as fixed plane",
"View navigator": "View navigator",
"Signed axes": "Signed axes",
"Cube": "Cube",
"Middle double-click": "Middle double-click",
"Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing",
"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: 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).",
"cmd.render_backends": "Render Backends",
"cmd.view_navigate": "Navigate View",
"ui.animateJoint": "Animate Joint",
"ui.calibrate": "Calibrate",
"ui.customizeToolbar": "Customize Toolbar…",
"ui.editJointLimits": "Edit Joint Limits",
"ui.exportDxf": "Export DXF…",
"ui.findInTimeline": "Find in Timeline",
"ui.findInWindow": "Find in Window",
"ui.fit": "Fit",
"ui.groupSelected": "Group Selected",
"ui.hideAxes": "Hide Axes",
"ui.hideDimension": "Hide Dimension",
"ui.hideOrigin": "Hide Origin",
"ui.hidePlanes": "Hide Planes",
"ui.hideProfile": "Hide Profile",
"ui.homeView": "Home View",
"ui.isolate": "Isolate",
"ui.lock": "Lock",
"ui.newSketchGroup": "New Sketch Group",
"ui.normalConstruction": "Normal / Construction",
"ui.orbit": "Orbit",
"ui.heading_position": "POSITION",
"ui.pan": "Pan",
"ui.pinToShortcuts": "Pin to Shortcuts",
"ui.pinToToolbar": "Pin to Toolbar",
"ui.properties": "Properties",
"ui.removeSection": "Remove Section",
"ui.removeFromToolbar": "Remove from Toolbar",
"ui.repeat": "Repeat",
"ui.report_a_bug": "Report a bug",
"ui.restore": "Restore",
"ui.heading_setup": "SETUP",
"ui.selectOther": "Select Other…",
"ui.selectTangentChain": "Select Tangent Chain",
"ui.showAll": "Show All",
"ui.showAllBodies": "Show All Bodies",
"ui.showAxes": "Show Axes",
"ui.showDimension": "Show Dimension",
"ui.showOrigin": "Show Origin",
"ui.showPlanes": "Show Planes",
"ui.suppress": "Suppress",
"ui.command_palette_hint": "Type a command, e.g. {example} — or press S to search",
"ui.unground": "Unground",
"ui.unisolate": "Unisolate",
"ui.unlock": "Unlock",
"ui.unsuppress": "Unsuppress",
"ui.updateToCurrentView": "Update to Current View",
"ui.zoom": "Zoom",
"ui.command_done": "{command} done",
"timeline.group": "New Feature Folder",
"timeline.ungroup": "Remove Feature Folders",
"timeline.rename_group": "Rename Feature Folder",
"timeline.move_to_group": "Move Features to Folder",
"timeline.move_many": "Move Timeline Items",
"timeline.reorder_many": "Reorder Features",
"ui.show_construction_geometries": "Construction Geometries",
"ui.total_angle": "Total angle",
"sketch_text.builtin_font": "{family} (built-in)",
"sketch_text.font_search": "Search fonts",
"sketch_text.loading_fonts": "Looking for installed fonts…",
"sketch_text.use_font_file": "Use the font file {path}",
"sketch_text.bold": "Bold",
"sketch_text.italic": "Italic",
"sketch_text.align_left": "Left",
"sketch_text.align_center": "Center",
"sketch_text.align_right": "Right",
"sketch_text.path_hint": "A curve of the sketch the text follows: click here, then the curve",
"sketch_text.path_clear": "Take the text off its curve",
"Style": "Style",
"Alignment": "Alignment",
"Letter Spacing": "Letter Spacing",
"Line Spacing": "Line Spacing",
"Path": "Path",
"sketch_text.on_path": "The text follows its path: the curve places it",
"cmd.material_properties_validate":"Validate Physical Material Data"
}
+961
View File
@@ -0,0 +1,961 @@
{
"browser.body_name": "ボディ{number}",
"browser.component_name": "コンポーネント{number}",
"browser.parameters": "パラメータ ({count})",
"browser.precision": "精度: {precision}",
"browser.sketch_name": "スケッチ{number}",
"browser.units": "単位: {units}",
"cmd.appearance_assign": "外観",
"cmd.appearance_library": "外観ライブラリ",
"cmd.appearance_list": "外観を一覧表示",
"cmd.body_rename": "ボディの名前を変更",
"cmd.browser_group": "新規グループ",
"cmd.browser_move": "ブラウザ項目を移動",
"cmd.browser_order": "ブラウザ項目の並び順を変更",
"cmd.browser_rename_group": "グループの名前を変更",
"cmd.browser_ungroup": "グループ解除",
"cmd.browser_visibility": "表示/非表示",
"cmd.canvas_calibrate": "キャンバスの尺度を設定",
"cmd.canvas_decal": "デカール",
"cmd.canvas_delete": "キャンバスを削除",
"cmd.canvas_edit": "キャンバスを編集",
"cmd.canvas_insert": "キャンバス",
"cmd.component_activate": "コンポーネントをアクティブ化",
"cmd.component_break_link": "リンクを解除",
"cmd.component_capture_position": "位置をキャプチャ",
"cmd.component_create": "新規コンポーネント",
"cmd.component_delete": "コンポーネントを削除",
"cmd.component_from_bodies": "ボディからコンポーネントを作成",
"cmd.component_insert_design": "デザインを挿入",
"cmd.component_links": "リンクを一覧表示",
"cmd.component_list": "コンポーネントを一覧表示",
"cmd.component_move_bodies": "ボディをコンポーネントに移動",
"cmd.component_move_sketches": "スケッチをコンポーネントに移動",
"cmd.component_paste_new": "新規として貼り付け",
"cmd.component_properties": "コンポーネントの物理的プロパティ",
"cmd.component_rename": "コンポーネントの名前を変更",
"cmd.component_revert_position": "位置を元に戻す",
"cmd.component_update_links": "リンクを更新",
"cmd.config_activate": "構成をアクティブ化",
"cmd.config_column": "構成列",
"cmd.config_configure": "構成を設定",
"cmd.config_preview": "構成をプレビュー",
"cmd.config_row": "構成行",
"cmd.config_rule": "構成ルール",
"cmd.config_rules": "構成ルール一覧",
"cmd.config_table": "構成テーブルを表示",
"cmd.construct_axis_edge": "エッジを通る軸",
"cmd.construct_axis_normal_to_face": "面に垂直な軸",
"cmd.construct_axis_through_cylinder": "円柱/円錐/トーラスを通る軸",
"cmd.construct_axis_two_planes": "2平面を通る軸",
"cmd.construct_axis_two_points": "2点を通る軸",
"cmd.construct_plane_along_path": "パスに沿った平面",
"cmd.construct_plane_midplane": "中間平面",
"cmd.construct_plane_perpendicular": "垂直平面",
"cmd.construct_plane_tangent": "正接平面",
"cmd.construct_plane_three_points": "3点を通る平面",
"cmd.construct_plane_two_edges": "2エッジを通る平面",
"cmd.construct_point_along_path": "パスに沿った点",
"cmd.construct_point_center": "円/球/トーラスの中心点",
"cmd.construct_point_edge_plane": "エッジと平面の交点",
"cmd.construct_point_three_planes": "3平面の交点",
"cmd.construct_point_two_edges": "2エッジの交点",
"cmd.construct_point_vertex": "頂点上の点",
"cmd.contact_check": "接触を確認",
"cmd.contact_create": "新規接触セット",
"cmd.contact_disable_all": "接触を無効化",
"cmd.contact_edit": "接触セットを編集",
"cmd.contact_enable_all": "すべての接触を有効化",
"cmd.contact_enable_sets": "接触セットを有効化",
"cmd.contact_list": "接触セットを一覧表示",
"cmd.diagnostics_crash_report": "クラッシュレポート",
"cmd.diagnostics_crash_reports": "クラッシュレポート一覧",
"cmd.diagnostics_crash_reports_clear": "クラッシュレポートを消去",
"cmd.diagnostics_crash_test": "クラッシュテスト",
"cmd.doc_recover": "デザインを復元",
"cmd.doc_recovery_discard": "復元デザインを破棄",
"cmd.doc_recovery_list": "復元可能なデザイン",
"cmd.document_inspect": "デザインを検査",
"cmd.document_units": "デザインの単位を変更",
"cmd.edit_checkpoint": "チェックポイント",
"cmd.edit_restore_checkpoint": "チェックポイントを復元",
"cmd.engine_commands": "コマンドを一覧表示",
"cmd.explode_create": "分解図",
"cmd.explode_delete": "分解図を削除",
"cmd.explode_list": "分解図を一覧表示",
"cmd.explode_show": "分解図を表示",
"cmd.expr_evaluate": "式を評価",
"cmd.face_delete": "面を削除",
"cmd.file_export": "エクスポート",
"cmd.file_insert_mesh": "メッシュを挿入",
"cmd.file_insert_step": "STEP を挿入",
"cmd.file_save_mesh": "メッシュとして保存",
"cmd.inspect_accessibility": "アクセシビリティ解析",
"cmd.inspect_center_of_mass": "重心",
"cmd.inspect_curvature_comb": "曲率コーム解析",
"cmd.inspect_curvature_map": "曲率マップ解析",
"cmd.inspect_draft": "勾配解析",
"cmd.inspect_environment_map": "環境マップ解析",
"cmd.inspect_interference": "干渉",
"cmd.inspect_isocurve": "アイソカーブ解析",
"cmd.inspect_minimum_radius": "最小半径解析",
"cmd.inspect_zebra": "ゼブラ解析",
"cmd.joint_as_built": "現位置ジョイント",
"cmd.joint_create": "ジョイント",
"cmd.joint_delete": "ジョイントを削除",
"cmd.joint_drive": "ジョイントを駆動",
"cmd.joint_edit": "ジョイントを編集",
"cmd.joint_limits": "ジョイントの制限",
"cmd.joint_list": "ジョイントを一覧表示",
"cmd.joint_motion_link": "モーション リンク",
"cmd.joint_origin": "ジョイント原点",
"cmd.joint_rigid_group": "リジッド グループ",
"cmd.joint_solve": "ジョイントを解決",
"cmd.material_assign": "物理マテリアル",
"cmd.material_create": "新規物理マテリアル",
"cmd.material_delete": "物理マテリアルを削除",
"cmd.material_edit": "物理マテリアルを編集",
"cmd.material_list": "マテリアルを一覧表示",
"cmd.mesh_convert": "メッシュを変換",
"cmd.model_edges": "エッジを一覧表示",
"cmd.model_faces": "面を一覧表示",
"cmd.model_threads": "ねじを一覧表示",
"cmd.motion_delete": "モーション スタディを削除",
"cmd.motion_export": "モーション スタディをエクスポート",
"cmd.motion_list": "モーション スタディを一覧表示",
"cmd.motion_play": "モーション スタディを再生",
"cmd.motion_study": "モーション スタディ",
"cmd.occurrence_copy": "貼り付け(インスタンス)",
"cmd.occurrence_ground": "空間固定",
"cmd.occurrence_move": "オカレンスを移動",
"cmd.occurrence_properties": "オカレンスの物理的プロパティ",
"cmd.parameters_add": "ユーザー パラメータを追加",
"cmd.parameters_change": "パラメータを変更",
"cmd.parameters_comment": "パラメータにコメントを付与",
"cmd.parameters_complete": "式を補完",
"cmd.parameters_delete": "パラメータを削除",
"cmd.parameters_export": "パラメータをエクスポート",
"cmd.parameters_favorite": "お気に入りパラメータ",
"cmd.parameters_graph": "パラメータ グラフ",
"cmd.parameters_import": "パラメータをインポート",
"cmd.parameters_list": "パラメータを一覧表示",
"cmd.parameters_rename": "パラメータの名前を変更",
"cmd.parameters_users": "パラメータの参照先",
"cmd.parts_fastener": "ファスナーを挿入",
"cmd.parts_insert": "パーツを挿入",
"cmd.parts_library": "標準パーツ",
"cmd.parts_resize": "パーツ サイズを変更",
"cmd.plastic_assign_rule": "プラスチック ルールを割り当て",
"cmd.plastic_boss": "ボス",
"cmd.plastic_lip": "リップ",
"cmd.plastic_manage_rules": "プラスチック ルールを管理",
"cmd.plastic_rest": "レスト",
"cmd.plastic_rules": "プラスチック ルールを一覧表示",
"cmd.plastic_snap_fit": "スナップ フィット",
"cmd.select_clear": "選択をクリア",
"cmd.sheet_convert": "板金に変換",
"cmd.sheet_export_bend_table": "曲げテーブルをエクスポート",
"cmd.sheet_export_dxf": "展開図を DXF としてエクスポート",
"cmd.sheet_flange": "フランジ",
"cmd.sheet_flat_pattern": "展開図を作成",
"cmd.sheet_fold": "折り曲げ",
"cmd.sheet_hem": "ヘミング",
"cmd.sheet_manage_rules": "板金ルール",
"cmd.sheet_refold": "折り戻し",
"cmd.sheet_rules": "板金ルールを一覧表示",
"cmd.sheet_unfold": "展開",
"cmd.sketch_arc_center_point": "中心点円弧",
"cmd.sketch_arc_tangent": "正接円弧",
"cmd.sketch_arc_three_point": "3点指定円弧",
"cmd.sketch_auto_constrain": "自動拘束",
"cmd.sketch_auto_constrain_from_datum": "基準から自動拘束",
"cmd.sketch_auto_project": "参照上のエッジを自動投影",
"cmd.sketch_blend_curve": "ブレンド曲線",
"cmd.sketch_chain": "チェーン",
"cmd.sketch_chamfer_distance_angle": "距離と角度の面取り",
"cmd.sketch_chamfer_equal_distance": "等距離面取り",
"cmd.sketch_chamfer_two_distance": "2距離面取り",
"cmd.sketch_circle_center": "中心と直径で指定した円",
"cmd.sketch_circle_three_point": "3点指定円",
"cmd.sketch_circle_three_tangent": "3接線指定の円",
"cmd.sketch_circle_two_point": "2点指定円",
"cmd.sketch_circle_two_tangent": "2接線指定の円",
"cmd.sketch_constrainer": "スケッチ拘束ツール",
"cmd.sketch_constraint_fix": "固定/固定解除",
"cmd.sketch_constraint_midpoint": "中点",
"cmd.sketch_construction": "標準/コンストラクション",
"cmd.sketch_delete": "スケッチ要素を削除",
"cmd.sketch_dimension_text": "寸法テキストを移動",
"cmd.sketch_drag": "スケッチ ジオメトリをドラッグ",
"cmd.sketch_drag_end": "スケッチのドラッグを終了",
"cmd.sketch_draw_dimensioned": "寸法付きスケッチ ジオメトリを描画",
"cmd.sketch_edit": "スケッチを編集",
"cmd.sketch_edit_text": "テキストを編集",
"cmd.sketch_export_dxf": "DXF として保存",
"cmd.sketch_finish_auto_constrain": "自動拘束で終了",
"cmd.sketch_fit_curves_to_section": "メッシュ断面に曲線をフィット",
"cmd.sketch_glyphs": "拘束記号",
"cmd.sketch_include_3d": "3D ジオメトリを含める",
"cmd.sketch_insert_dxf": "DXF を挿入",
"cmd.sketch_insert_svg": "SVG を挿入",
"cmd.sketch_inspect": "スケッチを検査",
"cmd.sketch_intersection_curve": "交差曲線",
"cmd.sketch_isoparametric_curve": "アイソパラメトリック曲線",
"cmd.sketch_line3d": "3D 線分",
"cmd.sketch_line_midpoint": "中点線分",
"cmd.sketch_move": "スケッチ要素を移動/コピー",
"cmd.sketch_move3d": "3D 点を移動",
"cmd.sketch_move_point": "スケッチ点をドラッグ",
"cmd.sketch_offset_break": "オフセット関連付けを解除",
"cmd.sketch_options": "スケッチ パレット オプション",
"cmd.sketch_point3d": "3D 点",
"cmd.sketch_polygon_circumscribed": "外接ポリゴン",
"cmd.sketch_polygon_edge": "エッジ ポリゴン",
"cmd.sketch_polygon_inscribed": "内接ポリゴン",
"cmd.sketch_project_to_surface": "サーフェスに投影",
"cmd.sketch_rectangle_center": "中心指定の長方形",
"cmd.sketch_rectangle_rounded": "角丸長方形",
"cmd.sketch_rectangle_three_point": "3点指定長方形",
"cmd.sketch_rectangle_two_point": "2点指定長方形",
"cmd.sketch_redefine": "スケッチ平面を再定義",
"cmd.sketch_slot_arc_center": "中心点円弧スロット",
"cmd.sketch_slot_arc_three_point": "3点円弧スロット",
"cmd.sketch_slot_center_point": "中心点スロット",
"cmd.sketch_slot_center_to_center": "中心間スロット",
"cmd.sketch_slot_overall": "全長指定スロット",
"cmd.sketch_snap": "スナップ推論",
"cmd.sketch_solve": "スケッチを解決",
"cmd.sketch_spline3d": "3D スプライン",
"cmd.sketch_spline_control_point": "制御点スプライン",
"cmd.sketch_spline_control_point_5": "制御点スプライン (次数 5)",
"cmd.sketch_spline_fit_point": "通過点スプライン",
"cmd.sketch_spun_profile": "回転プロファイル",
"cmd.sketch_toggle_driven": "従動寸法の切り替え",
"cmd.solid_align": "整列",
"cmd.solid_boundary_fill": "境界フィル",
"cmd.solid_bounding_solid": "バウンディング ソリッド",
"cmd.solid_emboss": "エンボス",
"cmd.solid_move": "移動/コピー",
"cmd.solid_offset_face": "面をオフセット",
"cmd.solid_press_pull": "プレス プル",
"cmd.solid_remove": "除去",
"cmd.solid_replace_face": "面を置換",
"cmd.solid_rib": "リブ",
"cmd.solid_split_face": "面を分割",
"cmd.solid_web": "ウェブ",
"cmd.surface_patch": "パッチ",
"cmd.surface_stitch": "ステッチ",
"cmd.surface_thicken": "厚み付け",
"cmd.surface_extrude": "サーフェス押し出し",
"cmd.surface_revolve": "サーフェス回転",
"cmd.surface_sweep": "サーフェススイープ",
"cmd.surface_loft": "サーフェスロフト",
"cmd.surface_ruled": "ルールドサーフェス",
"cmd.surface_boundary": "境界サーフェス",
"cmd.surface_offset": "オフセットサーフェス",
"cmd.surface_blend": "ブレンドサーフェス",
"cmd.surface_fillet": "サーフェスフィレット",
"cmd.surface_untrim": "トリム解除",
"cmd.surface_unstitch": "縫い合わせ解除",
"cmd.surface_reverse_normal": "法線を反転",
"cmd.inspect_continuity": "連続性解析",
"cmd.timeline_compute_all": "すべて再計算",
"cmd.timeline_copy": "フィーチャーをコピー",
"cmd.timeline_dependents": "従属フィーチャー",
"cmd.timeline_inspect": "フィーチャーを検査",
"cmd.timeline_paste": "フィーチャーを貼り付け",
"cmd.timeline_redefine": "フィーチャーを編集",
"cmd.timeline_rename": "名前を変更",
"cmd.timeline_reorder": "フィーチャーの順序を変更",
"cmd.timeline_roll_to": "履歴マーカーを移動",
"cmd.timeline_rollback": "タイムライン マーカーを移動",
"cmd.timeline_suppress": "フィーチャーを抑制",
"cmd.view_apply": "名前付きビューを復元",
"cmd.view_delete": "名前付きビューを削除",
"cmd.view_list": "名前付きビューを一覧表示",
"cmd.view_rename": "名前付きビューの名前を変更",
"cmd.view_save": "新規名前付きビュー",
"dialog.custom_axis": "↖ カスタム軸",
"dialog.edit_title": "{title}を編集",
"dialog.select": "↖ 選択",
"dialog.selected_count": "↖ {count} 個選択中",
"dialog.step": "ステップ",
"dialog.selection_step": "{total} ステップ中 {step}: {input} を選択",
"dialog.selection_guidance": "入力欄を選んで変更します。選択済みのオブジェクトをクリックすると解除できます。Ctrl/Cmd を押しながらクリックしても切り替えられます。プレビューを確認してから OK を押してください。",
"document.save_changes": "{name} への変更を保存しますか?",
"document.unsaved_changes": "{name} には保存されていない変更があります。保存しない場合、変更内容は失われます。",
"settings.msaa_applies_on_restart": "アンチエイリアスは次回 SolidCraft を起動したときに適用されます",
"settings.msaa_reset": "{setting}: {requested}× はこのグラフィックスデバイスではサポートされていないため、{reset}× に変更しました(サポート: {supported})。",
"settings.msaa_unsupported": "このグラフィックスデバイスではサポートされていません",
"settings.reset_intro": "次の設定はこのコンピューターでは使用できず、SolidCraft が起動できなくなるため変更されました:",
"settings.reset_title": "設定が変更されました",
"sketch.degrees_of_freedom": "自由度 {count}",
"sketch.draw_curves": "{linetype}として描く",
"sketch.make_selection": "選択を{linetype}にする",
"ui.1_degree_of_freedom": "自由度 1",
"ui.3d_mouse_speed": "3D マウス速度",
"ui.3d_mouse_windows": "3D マウス (Windows)",
"ui.3d_sketch": "3D スケッチ",
"ui.about": "SolidCraft について",
"ui.activate": "アクティブ化",
"ui.active_component": "アクティブなコンポーネント",
"ui.agent_cursor": "エージェントカーソル",
"ui.agent_cursor_speed": "エージェントカーソル速度",
"ui.all": "すべて",
"ui.analysis": "解析",
"ui.angle": "角度",
"ui.angle_decimals": "角度の小数点以下の桁数",
"ui.animate_view_changes": "視点変更をアニメーション表示",
"ui.animation": "アニメーション",
"ui.animation_speed": "アニメーション速度",
"ui.anti_aliasing": "アンチエイリアス",
"ui.apply": "適用",
"ui.arc": "円弧",
"ui.assemble": "アセンブリ",
"ui.autosave_every": "自動保存の間隔",
"ui.axes": "軸",
"ui.axis": "軸",
"ui.base_point": "基点",
"ui.bodies": "ボディ",
"ui.box": "直方体",
"ui.break": "分割",
"ui.cancel": "キャンセル",
"ui.canvases": "キャンバス",
"ui.center": "中心",
"ui.centerline": "中心線",
"ui.chain": "チェーン",
"ui.chamfer": "面取り",
"ui.chamfer_type": "面取りタイプ",
"ui.change_workspace": "ワークスペースの変更",
"ui.chord_length": "弦長",
"ui.curvature_g2": "曲率連続 (G2)",
"ui.equivalent_radius": "等価半径",
"ui.circle": "円",
"ui.circular_pattern": "円形パターン",
"ui.clear": "クリア",
"ui.clear_recent": "最近使った項目をクリア",
"ui.clear_the_selection": "選択をクリア",
"ui.click_selects_whole_bodies": "クリックでボディ全体を選択",
"ui.click_then_press_key": "クリックした後、割り当てるキーを押してください",
"ui.close": "閉じる",
"ui.coil": "コイル",
"ui.coincident": "一致",
"ui.collapse": "折りたたむ",
"ui.collinear": "同一直線上",
"ui.combine": "結合",
"ui.coming_later": "準備中",
"ui.concentric": "同心",
"ui.configurations": "構成",
"ui.conic_curve": "円錐曲線",
"ui.constant": "一定",
"ui.constraints_conflict": "拘束が競合しています",
"ui.construct": "構築",
"ui.construction": "コンストラクション",
"ui.context_menu": "コンテキスト メニュー",
"ui.copy": "コピー",
"ui.corner": "コーナー",
"ui.corners": "コーナー",
"ui.counterbore": "座ぐり",
"ui.countersink": "皿穴",
"ui.create": "作成",
"ui.create_copy": "コピーを作成",
"ui.create_sketch": "スケッチを作成",
"ui.current": "現在",
"ui.curvature": "曲率",
"ui.curves": "曲線",
"ui.cut": "切り取り",
"ui.cylinder": "円柱",
"ui.dark": "ダーク",
"ui.default_modelling_orientation": "モデリングのデフォルト方向",
"ui.degrees_of_freedom": "自由度",
"ui.delete": "削除",
"ui.depth": "深さ",
"ui.design": "デザイン",
"ui.diameter": "直径",
"ui.dimension": "寸法",
"ui.direction": "方向",
"ui.distance": "距離",
"ui.distance_1": "距離 1",
"ui.distance_2": "距離 2",
"ui.document_settings": "ドキュメントの設定",
"ui.don_t_save": "保存しない",
"ui.draft": "勾配",
"ui.drawing": "図面",
"ui.drill_point": "ドリル先端",
"ui.edit": "編集",
"ui.edit_section_analysis": "断面解析を編集",
"ui.ellipse": "楕円",
"ui.end_radius": "終了半径",
"ui.entities": "エンティティ",
"ui.equal": "等しい",
"ui.expand": "展開",
"ui.export_ellipsis": "エクスポート…",
"ui.export_ellipsis_keymap": "エクスポート…",
"ui.export_keymap_tip": "ショートカット、Sボックスのお気に入り、ツールバー設定をファイルに保存",
"ui.extend": "延長",
"ui.extent_type": "範囲タイプ",
"ui.extrude": "押し出し",
"ui.factor": "倍率",
"ui.feature_history": "フィーチャー履歴",
"ui.feature_options": "フィーチャーオプション",
"ui.features": "フィーチャー",
"ui.file": "ファイル",
"ui.fillet": "フィレット",
"ui.filter_commands_or_keys": "コマンドまたはキーを検索",
"ui.finish_sketch": "スケッチを終了",
"ui.fix_unfix": "固定/固定解除",
"ui.flat": "展開",
"ui.flip": "反転",
"ui.front": "正面",
"ui.fully_constrained": "完全拘束",
"ui.general": "一般",
"ui.graphics": "グラフィックス",
"ui.ground_shadow": "地面の影",
"ui.heading_add_ins": "アドイン",
"ui.heading_assemble": "アセンブリ",
"ui.heading_box": "直方体",
"ui.heading_chamfer": "面取り",
"ui.heading_circular_pattern": "円形パターン",
"ui.heading_coil": "コイル",
"ui.heading_combine": "結合",
"ui.heading_configure": "構成",
"ui.heading_constraints": "拘束",
"ui.heading_construct": "構築",
"ui.heading_create": "作成",
"ui.heading_create_sketch": "スケッチを作成",
"ui.heading_cylinder": "円柱",
"ui.heading_design": "デザイン",
"ui.heading_draft": "勾配",
"ui.heading_edit": "編集",
"ui.heading_extrude": "押し出し",
"ui.heading_fillet": "フィレット",
"ui.heading_finish_sketch": "スケッチを終了",
"ui.heading_front": "正面",
"ui.heading_hole": "穴",
"ui.heading_home": "ホーム",
"ui.heading_insert": "挿入",
"ui.heading_inspect": "検査",
"ui.heading_loft": "ロフト",
"ui.heading_make": "製造",
"ui.heading_measure": "計測",
"ui.heading_mesh": "メッシュ",
"ui.heading_mirror": "ミラー",
"ui.heading_modify": "修正",
"ui.heading_move": "移動",
"ui.heading_offset": "オフセット",
"ui.heading_move_copy": "移動/コピー",
"ui.heading_offset_plane": "オフセット平面",
"ui.heading_ok": "OK",
"ui.heading_parameters": "パラメータ",
"ui.heading_pattern_on_path": "パス上のパターン",
"ui.heading_pipe": "パイプ",
"ui.heading_plane_at_angle": "傾斜平面",
"ui.heading_plastic": "プラスチック",
"ui.heading_prepare": "準備",
"ui.heading_rectangular_pattern": "矩形パターン",
"ui.heading_revolve": "回転",
"ui.heading_right": "右側面",
"ui.heading_scale": "尺度",
"ui.heading_section_analysis": "断面解析",
"ui.heading_select": "選択",
"ui.heading_sheet_metal": "板金",
"ui.heading_shell": "シェル",
"ui.heading_sketch": "スケッチ",
"ui.heading_sketch_palette": "スケッチ パレット",
"ui.heading_sketch_scale": "スケッチの尺度",
"ui.heading_solid": "ソリッド",
"ui.heading_sphere": "球",
"ui.heading_split_body": "ボディを分割",
"ui.heading_surface": "サーフェス",
"ui.heading_sweep": "スイープ",
"ui.heading_thread": "ねじ",
"ui.heading_top": "上面",
"ui.heading_torus": "トーラス",
"ui.heading_utilities": "ユーティリティ",
"ui.heading_utility": "ユーティリティ",
"ui.help": "ヘルプ",
"ui.hide": "非表示",
"ui.hole": "穴",
"ui.hole_tap_type": "ねじ穴タイプ",
"ui.hole_type": "穴タイプ",
"ui.home": "ホーム",
"ui.horizontal_vertical": "水平/垂直",
"ui.import_ellipsis": "インポート…",
"ui.insert": "挿入",
"ui.inspect": "検査",
"ui.intersect": "交差",
"ui.join": "結合",
"ui.joints": "ジョイント",
"ui.keyboard_shortcuts": "キーボードショートカット",
"ui.language": "言語",
"ui.length_decimals": "長さの小数点以下の桁数",
"ui.light": "ライト",
"ui.line": "線分",
"ui.linetype": "線種",
"ui.loft": "ロフト",
"ui.look_at": "正面視",
"ui.look_at_anim_desc": "軸ビュー、正面視、ホーム、全体表示",
"ui.manufacture": "製造",
"ui.measure": "計測",
"ui.midpoint": "中点",
"ui.mirror": "ミラー",
"ui.mirror_line": "ミラー線",
"ui.modify": "修正",
"ui.move": "移動",
"ui.multiple": "複数",
"ui.named_views": "名前付きビュー",
"ui.navigation": "ナビゲーション",
"ui.needs_design": "{command}: 先にデザインを開いてください",
"ui.new_body": "新規ボディ",
"ui.new_design": "新規デザイン",
"ui.normal": "標準",
"ui.object_type": "オブジェクトタイプ",
"ui.objects": "オブジェクト",
"ui.offer_to_stand_up_y_up_files": "Y-upファイルを正立させるか確認",
"ui.offset": "オフセット",
"ui.offset_plane": "オフセット平面",
"ui.one_side": "片側",
"ui.open": "開く",
"ui.open_ellipsis": "開く…",
"ui.open_preferences": "環境設定を開く",
"ui.open_recent": "最近使った項目を開く",
"ui.operation": "操作",
"ui.options": "オプション",
"ui.orbit_around": "回転中心",
"ui.origin": "原点",
"ui.palette_points": "点",
"ui.palette_profile": "プロファイル",
"ui.pan_zoom_orbit": "パン、ズーム、オービット",
"ui.parallel": "平行",
"ui.parameters": "パラメータ",
"ui.paste": "貼り付け",
"ui.path": "パス",
"ui.pattern_on_path": "パス上のパターン",
"ui.perpendicular": "直交",
"ui.perspective_view": "パースペクティブ表示",
"ui.pipe": "パイプ",
"ui.placement": "配置",
"ui.plane": "平面",
"ui.plane_at_angle": "傾斜平面",
"ui.point": "点",
"ui.point_under_the_cursor": "カーソル下の点",
"ui.points": "点",
"ui.polygon": "ポリゴン",
"ui.preferences": "環境設定",
"ui.press_a_key": "キーを押してください…",
"ui.profile": "プロファイル",
"ui.profile_plane": "プロファイル平面",
"ui.profiles": "プロファイル",
"ui.project": "投影",
"ui.project_face_edges_sketch": "新規スケッチに面のエッジを自動投影",
"ui.quantity": "数量",
"ui.quantity_2": "数量 2",
"ui.quit": "終了",
"ui.radial_context_menu": "パイ メニュー",
"ui.radius": "半径",
"ui.radius_type": "半径タイプ",
"ui.rectangle": "長方形",
"ui.rectangular_pattern": "矩形パターン",
"ui.redo": "やり直す",
"ui.render": "レンダリング",
"ui.renderer": "レンダラー",
"ui.reset": "リセット",
"ui.reset_all": "すべてリセット",
"ui.reset_the_settings_on_this_page": "このページの設定をリセット",
"ui.restore_defaults": "初期値に戻す",
"ui.reverse_zoom_direction": "ズーム方向を反転",
"ui.revolve": "回転",
"ui.right": "右側面",
"ui.save": "保存",
"ui.save_as": "名前を付けて保存",
"ui.save_as_ellipsis": "名前を付けて保存…",
"ui.scale": "尺度",
"ui.section_analysis": "断面解析",
"ui.select": "選択",
"ui.select_other_hint": "その他を選択: モデル上で左ボタンを長押し(または右クリック › その他を選択…)するとカーソル下の要素が一覧表示されます。同じ場所を再度クリックすると次の要素が選択されます。",
"ui.selection": "選択",
"ui.shell": "シェル",
"ui.show": "表示",
"ui.show_constraints": "拘束",
"ui.show_dimensions": "寸法",
"ui.show_points": "点を表示",
"ui.show_profile": "プロファイルを表示",
"ui.show_projected_geometries": "投影ジオメトリ",
"ui.simple": "単純",
"ui.simulation": "シミュレーション",
"ui.single": "単一",
"ui.sketch": "スケッチ",
"ui.sketch_dimension": "スケッチ寸法",
"ui.sketch_grid": "スケッチ グリッド",
"ui.sketch_scale": "スケッチ尺度",
"ui.sketch_status": "スケッチの状態",
"ui.sketches": "スケッチ",
"ui.slice": "スライス",
"ui.slot": "スロット",
"ui.smooth": "スムーズ",
"ui.smooth_wheel_zoom": "滑らかなホイールズーム",
"ui.smooth_wheel_zoom_tip": "オフにすると、ホイール操作のたびにズームが即座に反映され、滑らかな減速効果は適用されません。",
"ui.snap": "スナップ",
"ui.space_mouse_hint": "視点の移動と回転を同時に行います(スケッチ中はパンとズームのみ)",
"ui.space_mouse_navigate": "SpaceNavigator / SpaceMouseで視点を操作",
"ui.spacing": "間隔",
"ui.spacing_2": "間隔 2",
"ui.sphere": "球",
"ui.spline": "スプライン",
"ui.split_body": "ボディを分割",
"ui.stand_up_tip": "Z軸上向きで開いたSTEPまたはIGESファイルに対し、X軸周りに90度回転させるか確認します",
"ui.start": "開始",
"ui.start_radius": "開始半径",
"ui.sweep": "スイープ",
"ui.symmetric": "対称",
"ui.symmetry": "対称",
"ui.system": "システム",
"ui.tangent": "正接",
"ui.taper_angle": "テーパ角度",
"ui.tapped": "ねじ穴",
"ui.text": "テキスト",
"ui.theme": "テーマ",
"ui.thickness": "厚さ",
"ui.thread": "ねじ",
"ui.timeline_bottom": "下部タイムライン",
"ui.timeline_left_note": "左レイアウトでは、ロールバックバーがパーツとシーンブラウザーの上に表示されます。",
"ui.timeline_left_sidebar": "左サイドバー",
"ui.tip_angle": "先端角度",
"ui.to_object": "オブジェクトまで",
"ui.top": "上面",
"ui.torus": "トーラス",
"ui.trackpad_pan_tip": "トラックパッドのジェスチャーでパンします(オフの場合はマウスホイールと同様にズームします)。マウスホイールはズーム、サイドホイールは横方向のパン、ピンチ操作は常にズームになります。",
"ui.trim": "トリム",
"ui.two_finger_scroll_pans": "2本指スクロールでパン",
"ui.two_sides": "両側",
"ui.ui_scale": "UIスケール",
"ui.undo": "元に戻す",
"ui.units": "単位",
"ui.units_of_new_designs": "新規デザインの単位",
"ui.untitled": "無題",
"ui.up_axis_note": "ビュー(軸ビュー、ホーム、正面視)で上向きにする軸を指定します。デザインデータ自体は変更されません。",
"ui.variable": "可変",
"ui.view_centre": "ビュー中心",
"ui.visual_shaded_hidden_edges": "隠線付きシェーディング",
"ui.visual_shaded_with_edges": "エッジ付きシェーディング",
"ui.visual_style": "表示スタイル",
"ui.visual_wireframe": "ワイヤーフレーム",
"ui.workspace": "ワークスペース",
"ui.x_distance": "X 距離",
"ui.y_distance": "Y 距離",
"ui.y_up": "Y軸上向き",
"ui.z_up": "Z軸上向き",
"ui.zoom_to_cursor_tip": "マウスホイールでカーソル位置を基準にズームします(オフの場合はビューの中心を基準にズームします)。",
"ui.zoom_to_the_cursor": "カーソル位置へズーム",
"A new configuration, a copy of the active one": "アクティブな構成をコピーして新しい構成を作成",
"About SolidCraft": "SolidCraft について",
"Active": "アクティブ",
"Active rule": "有効なルール",
"Add": "追加",
"Add Key": "キーを追加",
"Add Mid Radius": "中間半径を追加",
"Add Row": "行を追加",
"All Sketch Points": "すべてのスケッチ点",
"All open designs": "すべての開いているデザイン",
"Allowed expressions, separated by semicolons": "使用可能な式(セミコロンで区切る)",
"Also deletes": "同時に削除される要素:",
"Angle 2": "角度 2",
"Animate": "アニメーション",
"Appearance name": "外観名",
"Applied Radius": "適用半径",
"Apply To": "適用先",
"Area": "表面積",
"Attachment": "アタッチメント",
"BROWSER": "ブラウザ",
"Bend Line Position": "曲げ線位置",
"Bend Position": "曲げ位置",
"Bend Radius": "曲げ半径",
"Bends": "曲げ",
"Body": "ボディ",
"Bounding box": "境界ボックス",
"CSV or JSON: updates existing parameters, adds new ones": "CSVまたはJSON:既存のパラメータを更新し、新しいパラメータを追加します",
"Capture All Positions": "すべての位置をキャプチャ",
"Catch Direction": "キャッチ方向",
"Cells": "セル",
"Center X": "中心 X",
"Center Y": "中心 Y",
"Center of mass": "重心",
"Chamfer Type": "面取りタイプ",
"Check collisions": "衝突チェック",
"Checked": "チェック済み",
"Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "新しい平面を選択してください。スケッチジオメトリと寸法は2次元座標系のまま維持されます。",
"Close draft": "下書きを閉じる",
"Collisions": "衝突",
"Colour": "色",
"Component1": "コンポーネント1",
"Configuration": "構成",
"Configuration rules": "構成ルール",
"Contact": "接触",
"Copy all licences": "すべてのライセンスをコピー",
"Copy diagnostic information": "診断情報をコピー",
"Copy report": "レポートをコピー",
"Corner Relief Shape": "コーナーリリーフ形状",
"Counterbore depth": "座ぐり深さ",
"Countersink angle": "皿穴角度",
"Create Issue — review draft": "Issueを作成 — 下書きの確認",
"Customize Toolbar": "ツールバーのカスタマイズ",
"Delete all": "すべて削除",
"Delete rule": "ルールを削除",
"Delete the row": "行を削除",
"Delete the set": "セットを削除",
"Delete this parameter": "このパラメータを削除",
"Density": "密度",
"Designs you open or save appear here.": "開いたデザインや保存したデザインがここに表示されます。",
"Determined by To Object. Choose Angle to edit the angular extent.": "「対象オブジェクトまで」により決定されます。角度範囲を編集するには「角度」を選択してください。",
"Discard": "破棄",
"Double-click to rename": "ダブルクリックで名前変更",
"Drive Joint": "ジョイントを駆動",
"Edit Profile Sketch": "プロファイルスケッチを編集",
"Enable rule": "ルールを有効化",
"Enabled": "有効",
"Explode": "分解",
"Extents": "範囲",
"Face attachment lost": "面アタッチメントが失われました",
"Faces / edges / vertices": "面 / エッジ / 頂点",
"Favourite": "お気に入り",
"Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "多くのCADではY軸を上向きとしているため、Z軸を上向きとするSolidCraftでは、読み込んだモデルが横倒しの向きで表示されることがあります。",
"Filter": "フィルター",
"Find in Browser": "ブラウザで検索",
"Flat size": "展開サイズ",
"Follow camera (Look At sketches)": "カメラ追従(スケッチを正面視)",
"Font": "フォント",
"Full Length": "全長",
"Gap": "ギャップ",
"Grid": "グリッド",
"Height": "高さ",
"Home view": "ホームビュー",
"In This Design": "このデザイン内",
"Interferes": "干渉あり",
"Issue title": "Issueタイトル",
"Joint Origin1": "ジョイント原点1",
"Keep Tools": "ツールボディを保持",
"Later": "後で",
"Leave it": "そのままにする",
"Legacy face attachment. Rebinding to a certified face creates an exact attachment.": "従来の面アタッチメントです。確定された面に再バインドすると正確なアタッチメントが作成されます。",
"Length": "長さ",
"Length units": "長さの単位",
"Library": "ライブラリ",
"Loop": "ループ",
"Margin": "マージン",
"Mass": "質量",
"Material": "材料",
"Mid Radius": "中間半径",
"Modeled": "モデル化済み",
"More toolbar panels": "その他のツールバーパネル",
"Motion": "モーション",
"Move Type": "移動タイプ",
"Move earlier": "前へ移動",
"Move later": "後へ移動",
"Name": "名前",
"No crash reports.": "クラッシュレポートはありません。",
"No matching appearances": "一致する外観がありません",
"Not available yet": "まだ利用できません",
"Opacity": "不透明度",
"Open GitHub draft": "GitHubの下書きを開く",
"Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "Rust製オープンソース・パラメトリック3D CAD。ライセンス: MIT または Apache-2.0。",
"Other side": "反対側",
"Parameter values": "パラメータ値",
"Per Edge": "エッジごと",
"Pick the tool bodies": "ツールボディを選択",
"Place at the origin instead": "代わりに原点に配置",
"Playback": "再生",
"Position": "位置",
"Position (0 to 1)": "位置 (0〜1)",
"Position along the edge, 0 at the start end, 1 at the other": "エッジ上の位置(始点が0、終点が1)",
"Preview leaves the current design unchanged.": "プレビューは現在のデザインを変更しません。",
"RECENT": "最近使用した項目",
"Radius Fit": "半径フィット",
"Ratio": "比率",
"Recover": "復元",
"Recover unsaved design?": "未保存のデザインを復元しますか?",
"Remove Tools": "ツールボディを削除",
"Remove from list": "リストから削除",
"Remove private design details or paths before sharing.": "共有前に、非公開の設計情報やファイルパスを削除してください。",
"Remove the column": "列を削除",
"Remove the track": "トラックを削除",
"Remove this point": "この点を削除",
"Report a bug — review a screenshot and open a GitHub draft": "バグを報告 — スクリーンショットを確認してGitHub下書きを作成",
"Reset Toolbar": "ツールバーをリセット",
"Restore the inherited component or physical material appearance": "コンポーネントまたは物理マテリアルから継承した外観に戻す",
"Result": "結果",
"Retake screenshot": "スクリーンショットを再取得",
"Reverse": "反転",
"Reverse Direction": "方向を反転",
"Revert": "元に戻す",
"Revert All Positions": "すべての位置を元に戻す",
"Review the report and screenshot before opening GitHub.": "GitHubを開く前にレポートとスクリーンショットを確認してください。",
"Rim Edge": "リムエッジ",
"Roll History Marker Here": "履歴マーカーをここに移動",
"Rule": "ルール",
"Saved preview. Geometry is being checked before editing.": "保存済みのプレビューです。編集前にジオメトリを検証しています。",
"Scale factor": "尺度係数",
"Search": "検索",
"Search commands": "コマンドを検索",
"Select bodies (a face picks its body)": "ボディを選択(面をクリックするとそのボディが選択されます)",
"Select bodies or faces instead": "代わりにボディまたは面を選択",
"Selection filter": "選択フィルター",
"Semantic faces share their appearance across instances.": "セマンティック面はインスタンス間で外観を共有します。",
"Shape": "形状",
"Show Agent Cursor": "エージェントカーソルを表示",
"Show in folder": "フォルダに表示",
"Size": "サイズ",
"Sketch plane has no body-face attachment.": "スケッチ平面にボディ面のアタッチメントがありません。",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "これらのデザインが保存される前にSolidCraftが終了しました。最後の自動保存ファイルを開くことができます。",
"Speed": "速度",
"Stand it up": "正立させる",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "正立させる: 取り消し可能な1つの移動フィーチャーとして、X軸周りに90度回転します。",
"Stand this part up?": "このパーツを正立させますか?",
"Step": "ステップ",
"Steps": "ステップ数",
"Suppressed": "抑制",
"Swap Ends": "端点を入れ替え",
"Tangent Chain": "正接チェーン",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "破線のスケッチは保存された平面ガイドです。新しい面を選択してアタッチメントを修復してください。",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "保存された作成元のオカレンスが見つかりません。再バインドする前に有効な作成元配置を選択してください。",
"Then": "次を実行 (Then)",
"These features will be deleted:": "以下のフィーチャーが削除されます:",
"These features will fail:": "以下のフィーチャーでエラーが発生します:",
"This child is shared. Deleting it removes it from every copy of its parent.": "この子要素は共有されています。削除すると親のすべてのコピーから削除されます。",
"This child is shared. Position changes apply to every copy of its parent.": "この子要素は共有されています。位置の変更は親のすべてのコピーに適用されます。",
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "この面は安定したアタッチメントを提供できません。別の面を選択するか、固定平面を明示的に使用してください。",
"To Offset": "オフセットまで",
"Tolerance": "公差",
"Type": "タイプ",
"Updating the preview": "プレビューを更新中",
"Use fixed plane": "固定平面を使用",
"VIEW": "表示",
"Value": "値",
"Volume": "体積",
"Wall": "肉厚",
"When": "条件 (When)",
"Width": "幅",
"Will fail": "失敗します",
"angle, height": "角度, 高さ",
"command name": "コマンド名",
"expression, e.g. 25 mm": "計算式 (例: 25 mm)",
"fits the face, e.g. M8": "面に適合するサイズ (例: M8)",
"from the rule": "ルールから",
"needs two joints": "2つのジョイントが必要",
"new configuration": "新規構成",
"new name": "新しい名前",
"new parameter": "新規パラメータ",
"no interference": "干渉なし",
"no joint that moves yet": "可動ジョイントがまだありません",
"no picture": "画像なし",
"pick a hole to seat it, or OK places it at the origin": "配置先の穴を選択するか、[OK]を押すと原点に配置します。",
"pick one or two vertices, edges or faces": "1つまたは2つの頂点、エッジ、面を選択",
"the parts library is not available": "パーツライブラリは利用できません",
"to the next face": "次の面まで",
"Revert Position": "位置を元に戻す",
"Construction": "コンストラクション",
"Flat": "展開",
"Faces": "面",
"Edges": "エッジ",
"Vertices": "頂点",
"Sketch geometry and profiles": "スケッチジオメトリとプロファイル",
"Sketch curves": "スケッチ曲線",
"Sketch points": "スケッチ点",
"Sketch dimensions": "スケッチ寸法",
"Sketch constraints": "スケッチ拘束",
"0 turns it off": "0で無効化",
"Show the pointer an AI agent moves (#35)": "エージェントが操作するポインターを表示 (#35)",
"Follow the camera (Look At sketches)": "カメラ追従(スケッチを正面視)",
"Save Rule": "ルールを保存",
"Preview Row": "行をプレビュー",
"Add condition": "条件を追加",
"Apply to selection": "選択項目に適用",
"Remove override": "オーバーライドを削除",
"Copy screenshot": "スクリーンショットをコピー",
"Remove screenshot": "スクリーンショットを削除",
"Use active author placement": "アクティブな作成元配置を使用",
"Reattach selected face": "選択した面を再アタッチ",
"Use saved fixed plane": "保存された固定平面を使用",
"Use selected face as fixed plane": "選択した面を固定平面として使用",
"View navigator": "ビューナビゲーター",
"Signed axes": "符号付き座標軸",
"Cube": "キューブ",
"Middle double-click": "中ボタンのダブルクリック",
"Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "ビュー上で中ボタンをダブルクリックした時の動作: ホーム(全体を収めたホームビュー)、全体表示(向きを維持して全体を収める)、または無効",
"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).": "その他を選択: モデル上で左ボタンを長押し(または右クリック › その他を選択…)するとカーソル下の要素が一覧表示されます。同じ場所を再度クリックすると次の要素が選択されます。中ボタンをダブルクリックするとホームビューに戻ります(環境設定 › ナビゲーション)。",
"cmd.render_backends": "レンダリング バックエンド",
"cmd.view_navigate": "ビューナビゲーション",
"ui.animateJoint": "ジョイントをアニメート",
"ui.calibrate": "校正",
"ui.customizeToolbar": "ツールバーをカスタマイズ…",
"ui.editJointLimits": "ジョイント制限を編集",
"ui.exportDxf": "DXF をエクスポート…",
"ui.findInTimeline": "タイムラインで検索",
"ui.findInWindow": "ウィンドウで検索",
"ui.fit": "フィット",
"ui.groupSelected": "選択をグループ化",
"ui.hideAxes": "軸を非表示",
"ui.hideDimension": "寸法を非表示",
"ui.hideOrigin": "原点を非表示",
"ui.hidePlanes": "平面を非表示",
"ui.hideProfile": "プロファイルを非表示",
"ui.homeView": "ホームビュー",
"ui.isolate": "分離表示",
"ui.lock": "ロック",
"ui.newSketchGroup": "新しいスケッチグループ",
"ui.normalConstruction": "通常 / コンストラクション",
"ui.orbit": "回転",
"ui.heading_position": "配置",
"ui.pan": "パン",
"ui.pinToShortcuts": "ショートカットに固定",
"ui.pinToToolbar": "ツールバーに固定",
"ui.properties": "プロパティ",
"ui.removeSection": "セクションを解除",
"ui.removeFromToolbar": "ツールバーから削除",
"ui.repeat": "繰り返し",
"ui.report_a_bug": "不具合を報告",
"ui.restore": "復元",
"ui.heading_setup": "セットアップ",
"ui.selectOther": "その他を選択…",
"ui.selectTangentChain": "接線チェーンを選択",
"ui.showAll": "すべて表示",
"ui.showAllBodies": "すべてのボディを表示",
"ui.showAxes": "軸を表示",
"ui.showDimension": "寸法を表示",
"ui.showOrigin": "原点を表示",
"ui.showPlanes": "平面を表示",
"ui.suppress": "抑制",
"ui.command_palette_hint": "コマンドを入力(例: {example})— または S キーで検索",
"ui.unground": "固定を解除",
"ui.unisolate": "分離表示を解除",
"ui.unlock": "ロック解除",
"ui.unsuppress": "抑制を解除",
"ui.updateToCurrentView": "現在のビューに更新",
"ui.zoom": "ズーム",
"ui.command_done": "{command} 完了",
"timeline.group": "フィーチャーフォルダを新規作成",
"timeline.ungroup": "フィーチャーフォルダを削除",
"timeline.rename_group": "フィーチャーフォルダの名前を変更",
"timeline.move_to_group": "フィーチャーをフォルダへ移動",
"timeline.move_many": "タイムライン項目を移動",
"timeline.reorder_many": "フィーチャーの順序を変更",
"ui.show_construction_geometries": "構築ジオメトリ",
"ui.total_angle": "合計角度",
"sketch_text.builtin_font": "{family}(内蔵)",
"sketch_text.font_search": "フォントを検索",
"sketch_text.loading_fonts": "インストール済みフォントを検索中…",
"sketch_text.use_font_file": "フォント ファイル {path} を使用",
"sketch_text.bold": "太字",
"sketch_text.italic": "斜体",
"sketch_text.align_left": "左",
"sketch_text.align_center": "中央",
"sketch_text.align_right": "右",
"sketch_text.path_hint": "文字が沿うスケッチの曲線: ここをクリックしてから曲線をクリック",
"sketch_text.path_clear": "文字を曲線から外す",
"Style": "スタイル",
"Alignment": "配置",
"Letter Spacing": "文字間隔",
"Line Spacing": "行間",
"Path": "パス",
"sketch_text.on_path": "文字はパスに沿います: 位置は曲線で決まります",
"cmd.material_properties_validate":"物理材料データを検証"
}
+701
View File
@@ -0,0 +1,701 @@
{
"browser.body_name": "바디{number}",
"browser.component_name": "컴포넌트{number}",
"browser.parameters": "매개변수 ({count})",
"browser.precision": "정밀도: {precision}",
"browser.sketch_name": "스케치{number}",
"browser.units": "단위: {units}",
"dialog.custom_axis": "↖ 사용자 정의 축",
"dialog.edit_title": "{title} 편집",
"dialog.select": "↖ 선택",
"dialog.selected_count": "↖ {count}개 선택됨",
"dialog.step": "단계",
"dialog.selection_step": "{step}/{total} 단계: {input} 선택",
"dialog.selection_guidance": "변경할 입력을 선택하세요. 선택한 객체를 클릭하면 선택이 해제되며, Ctrl/Cmd를 누른 채 클릭해도 전환됩니다. 미리 보기를 확인한 다음 확인을 누르세요.",
"document.save_changes": "{name}의 변경 사항을 저장하시겠습니까?",
"document.unsaved_changes": "{name}에 저장되지 않은 변경 사항이 있습니다. 저장하지 않으면 변경 사항이 사라집니다.",
"settings.msaa_applies_on_restart": "안티앨리어싱은 다음에 SolidCraft를 시작할 때 적용됩니다",
"settings.msaa_reset": "{setting}: {requested}×는 이 그래픽 장치에서 지원되지 않아 {reset}×로 변경했습니다(지원: {supported}).",
"settings.msaa_unsupported": "이 그래픽 장치에서 지원되지 않음",
"settings.reset_intro": "다음 설정은 이 컴퓨터에서 작동하지 않아 SolidCraft를 시작할 수 없게 하므로 변경되었습니다:",
"settings.reset_title": "설정이 변경되었습니다",
"sketch.degrees_of_freedom": "자유도 {count}개",
"sketch.draw_curves": "{linetype}(으)로 그리기",
"sketch.make_selection": "선택 항목을 {linetype}(으)로 변경",
"ui.1_degree_of_freedom": "자유도 1개",
"ui.3d_sketch": "3D 스케치",
"ui.about": "정보",
"ui.activate": "활성화",
"ui.active_component": "활성 컴포넌트",
"ui.agent_cursor": "에이전트 커서",
"ui.agent_cursor_speed": "에이전트 커서 속도",
"ui.all": "모두",
"ui.analysis": "분석",
"ui.angle": "각도",
"ui.angle_decimals": "각도 소수점 자릿수",
"ui.animate_view_changes": "뷰 변경 애니메이션",
"ui.animation": "애니메이션",
"ui.animation_speed": "애니메이션 속도",
"ui.anti_aliasing": "안티앨리어싱",
"ui.apply": "적용",
"ui.arc": "호",
"ui.assemble": "조립",
"ui.autosave_every": "자동 저장 간격",
"ui.axes": "축",
"ui.axis": "축",
"ui.base_point": "기준점",
"ui.bodies": "바디",
"ui.box": "박스",
"ui.break": "끊기",
"ui.cancel": "취소",
"ui.canvases": "캔버스",
"ui.center": "중심",
"ui.centerline": "중심선",
"ui.chain": "체인",
"ui.chamfer": "모따기",
"ui.chamfer_type": "모따기 유형",
"ui.change_workspace": "작업 공간 변경",
"ui.chord_length": "현 길이",
"ui.curvature_g2": "곡률 연속 (G2)",
"ui.equivalent_radius": "등가 반지름",
"ui.circle": "원",
"ui.circular_pattern": "원형 패턴",
"ui.clear_recent": "최근 항목 지우기",
"ui.clear_the_selection": "선택 해제",
"ui.close": "닫기",
"ui.coil": "코일",
"ui.coincident": "일치",
"ui.collapse": "접기",
"ui.collinear": "동일 선상",
"ui.combine": "결합",
"ui.coming_later": "추후 지원 예정",
"ui.concentric": "동심",
"ui.configurations": "구성",
"ui.conic_curve": "원뿔 곡선",
"ui.constant": "상수",
"ui.constraints_conflict": "구속 조건 충돌",
"ui.construct": "구성",
"ui.construction": "구성선",
"ui.copy": "복사",
"ui.corners": "코너",
"ui.corner": "코너",
"ui.counterbore": "카운터보어",
"ui.countersink": "카운터싱크",
"ui.create": "만들기",
"ui.create_copy": "복사본 만들기",
"ui.create_sketch": "스케치 만들기",
"ui.current": "현재",
"ui.curvature": "곡률",
"ui.curves": "곡선",
"ui.cut": "잘라내기",
"ui.cylinder": "원통",
"ui.dark": "어둡게",
"ui.default_modelling_orientation": "기본 모델링 방향",
"ui.degrees_of_freedom": "자유도",
"ui.delete": "삭제",
"ui.depth": "깊이",
"ui.design": "디자인",
"ui.diameter": "지름",
"ui.dimension": "치수",
"ui.direction": "방향",
"ui.distance": "거리",
"ui.distance_1": "거리 1",
"ui.distance_2": "거리 2",
"ui.document_settings": "문서 설정",
"ui.don_t_save": "저장 안 함",
"ui.draft": "구배",
"ui.drawing": "도면",
"ui.drill_point": "드릴 선단",
"ui.edit": "편집",
"ui.edit_section_analysis": "단면 분석 편집",
"ui.ellipse": "타원",
"ui.end_radius": "끝 반지름",
"ui.entities": "요소",
"ui.equal": "동일",
"ui.expand": "펼치기",
"ui.export_ellipsis": "내보내기...",
"ui.extend": "연장",
"ui.extent_type": "범위 유형",
"ui.extrude": "돌출",
"ui.factor": "배율",
"ui.feature_history": "피처 기록",
"ui.feature_options": "피처 옵션",
"ui.features": "피처",
"ui.file": "파일",
"ui.fillet": "필렛",
"ui.finish_sketch": "스케치 종료",
"ui.fix_unfix": "고정/고정 해제",
"ui.flat": "플랫",
"ui.flip": "뒤집기",
"ui.front": "정면",
"ui.fully_constrained": "완전 구속",
"ui.general": "일반",
"ui.graphics": "그래픽",
"ui.heading_assemble": "조립",
"ui.heading_box": "박스",
"ui.heading_chamfer": "모따기",
"ui.heading_circular_pattern": "원형 패턴",
"ui.heading_coil": "코일",
"ui.heading_combine": "결합",
"ui.heading_construct": "구성",
"ui.heading_create": "만들기",
"ui.heading_create_sketch": "스케치 만들기",
"ui.heading_cylinder": "원통",
"ui.heading_design": "디자인",
"ui.heading_draft": "구배",
"ui.heading_edit": "편집",
"ui.heading_extrude": "돌출",
"ui.heading_fillet": "필렛",
"ui.heading_finish_sketch": "스케치 종료",
"ui.heading_front": "정면",
"ui.heading_hole": "구멍",
"ui.heading_home": "홈",
"ui.heading_insert": "삽입",
"ui.heading_inspect": "검사",
"ui.heading_loft": "로프트",
"ui.heading_measure": "측정",
"ui.heading_mesh": "메시",
"ui.heading_mirror": "대칭 복사",
"ui.heading_modify": "수정",
"ui.heading_move": "이동",
"ui.heading_offset": "오프셋",
"ui.heading_move_copy": "이동/복사",
"ui.heading_offset_plane": "오프셋 평면",
"ui.heading_ok": "확인",
"ui.heading_parameters": "매개변수",
"ui.heading_pattern_on_path": "경로 패턴",
"ui.heading_pipe": "파이프",
"ui.heading_plane_at_angle": "각도 평면",
"ui.heading_plastic": "플라스틱",
"ui.heading_rectangular_pattern": "직사각형 패턴",
"ui.heading_revolve": "회전",
"ui.heading_right": "우측면",
"ui.heading_scale": "축척",
"ui.heading_section_analysis": "단면 분석",
"ui.heading_select": "선택",
"ui.heading_sheet_metal": "판금",
"ui.heading_shell": "쉘",
"ui.heading_sketch": "스케치",
"ui.heading_sketch_palette": "스케치 팔레트",
"ui.heading_sketch_scale": "스케치 배율",
"ui.heading_solid": "솔리드",
"ui.heading_sphere": "구",
"ui.heading_split_body": "바디 분할",
"ui.heading_surface": "서피스",
"ui.heading_sweep": "스윕",
"ui.heading_thread": "나사산",
"ui.heading_top": "상면",
"ui.heading_torus": "토러스",
"ui.help": "도움말",
"ui.hide": "숨기기",
"ui.hole": "구멍",
"ui.hole_tap_type": "탭 유형",
"ui.hole_type": "구멍 유형",
"ui.home": "홈",
"ui.horizontal_vertical": "수평/수직",
"ui.insert": "삽입",
"ui.inspect": "검사",
"ui.intersect": "교차",
"ui.join": "접합",
"ui.joints": "조인트",
"ui.keyboard_shortcuts": "키보드 단축키",
"ui.language": "언어",
"ui.length_decimals": "길이 소수점 자릿수",
"ui.light": "밝게",
"ui.line": "선",
"ui.linetype": "선 종류",
"ui.loft": "로프트",
"ui.look_at": "바라보기",
"ui.manufacture": "제조",
"ui.measure": "측정",
"ui.midpoint": "중간점",
"ui.mirror": "대칭 복사",
"ui.mirror_line": "대칭선",
"ui.modify": "수정",
"ui.move": "이동",
"ui.multiple": "다중",
"ui.named_views": "명명된 뷰",
"ui.navigation": "탐색",
"ui.needs_design": "{command}: 먼저 디자인을 열어 주세요",
"ui.new_body": "새 바디",
"ui.new_design": "새 디자인",
"ui.normal": "일반",
"ui.object_type": "객체 유형",
"ui.objects": "객체",
"ui.offer_to_stand_up_y_up_files": "Y-up 파일을 Z-up으로 세우도록 제안",
"ui.offset": "오프셋",
"ui.offset_plane": "오프셋 평면",
"ui.one_side": "한 방향",
"ui.open": "열기",
"ui.open_ellipsis": "열기...",
"ui.open_preferences": "환경 설정 열기",
"ui.open_recent": "최근 항목 열기",
"ui.operation": "작업",
"ui.options": "옵션",
"ui.orbit_around": "궤도 회전 기준",
"ui.origin": "원점",
"ui.palette_points": "점",
"ui.palette_profile": "프로파일",
"ui.pan_zoom_orbit": "이동, 확대/축소, 궤도 회전",
"ui.parallel": "평행",
"ui.parameters": "매개변수",
"ui.paste": "붙여넣기",
"ui.path": "경로",
"ui.pattern_on_path": "경로 패턴",
"ui.perpendicular": "수직",
"ui.pipe": "파이프",
"ui.placement": "배치",
"ui.plane": "평면",
"ui.plane_at_angle": "각도 평면",
"ui.point": "점",
"ui.point_under_the_cursor": "커서 아래의 점",
"ui.points": "점",
"ui.polygon": "다각형",
"ui.preferences": "환경 설정",
"ui.profile": "프로파일",
"ui.profile_plane": "프로파일 평면",
"ui.profiles": "프로파일",
"ui.project": "투영",
"ui.quantity_2": "수량 2",
"ui.quantity": "수량",
"ui.quit": "종료",
"ui.radius": "반지름",
"ui.radius_type": "반지름 유형",
"ui.rectangle": "직사각형",
"ui.rectangular_pattern": "직사각형 패턴",
"ui.redo": "다시 실행",
"ui.render": "렌더",
"ui.reset_the_settings_on_this_page": "이 페이지의 설정 초기화",
"ui.restore_defaults": "기본값 복원",
"ui.reverse_zoom_direction": "확대/축소 방향 반전",
"ui.revolve": "회전",
"ui.right": "우측면",
"ui.save": "저장",
"ui.save_as": "다른 이름으로 저장",
"ui.save_as_ellipsis": "다른 이름으로 저장...",
"ui.scale": "축척",
"ui.section_analysis": "단면 분석",
"ui.select": "선택",
"ui.selection": "선택",
"ui.shell": "쉘",
"ui.show": "표시",
"ui.show_constraints": "구속 조건",
"ui.show_dimensions": "치수",
"ui.show_points": "점 표시",
"ui.show_profile": "프로파일 표시",
"ui.show_projected_geometries": "투영된 형상",
"ui.simple": "단순",
"ui.simulation": "시뮬레이션",
"ui.single": "단일",
"ui.sketch": "스케치",
"ui.sketch_dimension": "스케치 치수",
"ui.sketch_grid": "스케치 격자",
"ui.sketch_scale": "스케치 축척",
"ui.sketch_status": "스케치 상태",
"ui.sketches": "스케치",
"ui.slice": "슬라이스",
"ui.slot": "슬롯",
"ui.smooth": "부드럽게",
"ui.snap": "스냅",
"ui.spacing_2": "간격 2",
"ui.spacing": "간격",
"ui.sphere": "구",
"ui.spline": "스플라인",
"ui.split_body": "바디 분할",
"ui.start": "시작",
"ui.start_radius": "시작 반지름",
"ui.sweep": "스윕",
"ui.symmetric": "대칭",
"ui.symmetry": "대칭",
"ui.system": "시스템",
"ui.tangent": "접선",
"ui.taper_angle": "테이퍼 각도",
"ui.tapped": "탭",
"ui.text": "텍스트",
"ui.theme": "테마",
"ui.thickness": "두께",
"ui.thread": "나사산",
"ui.timeline_bottom": "하단 타임라인",
"ui.timeline_left_note": "왼쪽 레이아웃에서는 롤백 바가 부품 및 장면 브라우저 위에 표시됩니다.",
"ui.timeline_left_sidebar": "왼쪽 사이드바",
"ui.tip_angle": "선단 각도",
"ui.to_object": "객체까지",
"ui.top": "상면",
"ui.torus": "토러스",
"ui.trim": "트림",
"ui.two_sides": "양방향",
"ui.ui_scale": "UI 배율",
"ui.undo": "실행 취소",
"ui.units": "단위",
"ui.units_of_new_designs": "새 디자인의 단위",
"ui.untitled": "제목 없음",
"ui.variable": "가변",
"ui.view_centre": "뷰 중심",
"ui.visual_style": "시각 스타일",
"ui.workspace": "작업 공간",
"ui.y_distance": "Y 거리",
"ui.x_distance": "X 거리",
"ui.zoom_to_the_cursor": "커서 위치로 확대/축소",
"ui.animateJoint": "조인트 애니메이션",
"ui.calibrate": "캘리브레이션",
"ui.customizeToolbar": "도구 모음 사용자 지정…",
"ui.editJointLimits": "조인트 제한 편집",
"ui.exportDxf": "DXF 내보내기…",
"ui.findInTimeline": "타임라인에서 찾기",
"ui.findInWindow": "창에서 찾기",
"ui.fit": "맞춤",
"ui.groupSelected": "선택 항목 그룹화",
"ui.hideAxes": "축 숨기기",
"ui.hideDimension": "치수 숨기기",
"ui.hideOrigin": "원점 숨기기",
"ui.hidePlanes": "평면 숨기기",
"ui.hideProfile": "윤곽 숨기기",
"ui.homeView": "홈 보기",
"ui.isolate": "분리 표시",
"ui.lock": "잠금",
"ui.newSketchGroup": "새 스케치 그룹",
"ui.normalConstruction": "일반 / 생성",
"ui.orbit": "회전",
"ui.heading_position": "배치",
"ui.pan": "이동",
"ui.pinToShortcuts": "바로 가기에 고정",
"ui.pinToToolbar": "도구 모음에 고정",
"ui.properties": "속성",
"ui.removeSection": "단면 제거",
"ui.removeFromToolbar": "도구 모음에서 제거",
"ui.repeat": "반복",
"ui.report_a_bug": "버그 신고",
"ui.restore": "복원",
"ui.heading_setup": "설정",
"ui.selectOther": "기타 선택…",
"ui.selectTangentChain": "접선 체인 선택",
"ui.showAll": "모두 표시",
"ui.showAllBodies": "모두 표시",
"ui.showAxes": "축 표시",
"ui.showDimension": "치수 표시",
"ui.showOrigin": "원점 표시",
"ui.showPlanes": "평면 표시",
"ui.suppress": "비활성화",
"ui.command_palette_hint": "명령 입력(예: {example}) — 또는 S 키를 눌러 검색",
"ui.unground": "고정 해제",
"ui.unisolate": "분리 표시 해제",
"ui.unlock": "잠금 해제",
"ui.unsuppress": "비활성화 해제",
"ui.updateToCurrentView": "현재 보기로 업데이트",
"ui.zoom": "확대/축소",
"ui.command_done": "{command} 완료",
"cmd.appearance_assign": "외관",
"cmd.appearance_library": "외관 라이브러리",
"cmd.appearance_list": "외관 목록",
"cmd.body_rename": "바디 이름 변경",
"cmd.browser_group": "새 그룹",
"cmd.browser_move": "브라우저 항목 이동",
"cmd.browser_order": "브라우저 항목 정렬",
"cmd.browser_rename_group": "그룹 이름 변경",
"cmd.browser_ungroup": "그룹 해제",
"cmd.browser_visibility": "표시/숨기기",
"cmd.canvas_calibrate": "캔버스 보정",
"cmd.canvas_decal": "데칼",
"cmd.canvas_delete": "캔버스 삭제",
"cmd.canvas_edit": "캔버스 편집",
"cmd.canvas_insert": "캔버스",
"cmd.component_activate": "구성 요소 활성화",
"cmd.component_break_link": "링크 끊기",
"cmd.component_capture_position": "위치 캡처",
"cmd.component_create": "새 구성 요소",
"cmd.component_delete": "구성 요소 삭제",
"cmd.component_from_bodies": "바디로 구성 요소 만들기",
"cmd.component_insert_design": "설계 삽입",
"cmd.component_links": "링크 목록",
"cmd.component_list": "구성 요소 목록",
"cmd.component_move_bodies": "바디를 구성 요소로 이동",
"cmd.component_move_sketches": "스케치를 구성 요소로 이동",
"cmd.component_paste_new": "새로 붙여넣기",
"cmd.component_properties": "구성 요소 물리 속성",
"cmd.component_rename": "구성 요소 이름 변경",
"cmd.component_revert_position": "위치 복원",
"cmd.component_update_links": "링크 업데이트",
"cmd.config_activate": "구성 활성화",
"cmd.config_column": "구성 열",
"cmd.config_configure": "구성",
"cmd.config_preview": "구성 미리 보기",
"cmd.config_row": "구성 행",
"cmd.config_rule": "구성 규칙",
"cmd.config_rules": "구성 규칙",
"cmd.config_table": "구성 표시",
"cmd.construct_axis_edge": "모서리를 통과하는 축",
"cmd.construct_axis_normal_to_face": "면에 수직인 축",
"cmd.construct_axis_through_cylinder": "원기둥/원뿔/토러스를 통과하는 축",
"cmd.construct_axis_two_planes": "두 평면을 통과하는 축",
"cmd.construct_axis_two_points": "두 점을 통과하는 축",
"cmd.construct_plane_along_path": "경로를 따르는 평면",
"cmd.construct_plane_midplane": "중간 평면",
"cmd.construct_plane_perpendicular": "수직 평면",
"cmd.construct_plane_tangent": "접선 평면",
"cmd.construct_plane_three_points": "세 점을 통과하는 평면",
"cmd.construct_plane_two_edges": "두 모서리를 통과하는 평면",
"cmd.construct_point_along_path": "경로를 따르는 점",
"cmd.construct_point_center": "원/구/토러스 중심의 점",
"cmd.construct_point_edge_plane": "모서리와 평면의 점",
"cmd.construct_point_three_planes": "세 평면이 만나는 점",
"cmd.construct_point_two_edges": "두 모서리가 만나는 점",
"cmd.construct_point_vertex": "꼭짓점의 점",
"cmd.contact_check": "접촉 확인",
"cmd.contact_create": "새 접촉 집합",
"cmd.contact_disable_all": "접촉 비활성화",
"cmd.contact_edit": "접촉 집합 편집",
"cmd.contact_enable_all": "모든 접촉 활성화",
"cmd.contact_enable_sets": "접촉 집합 활성화",
"cmd.contact_list": "접촉 집합 목록",
"cmd.diagnostics_crash_report": "충돌 보고서",
"cmd.diagnostics_crash_reports": "충돌 보고서",
"cmd.diagnostics_crash_reports_clear": "충돌 보고서 삭제",
"cmd.diagnostics_crash_test": "충돌 테스트",
"cmd.doc_recover": "설계 복구",
"cmd.doc_recovery_discard": "복구된 설계 폐기",
"cmd.doc_recovery_list": "복구 가능한 설계",
"cmd.document_inspect": "설계 검사",
"cmd.document_units": "설계 단위 변경",
"cmd.edit_checkpoint": "체크포인트",
"cmd.edit_restore_checkpoint": "체크포인트 복원",
"cmd.engine_commands": "명령 목록",
"cmd.explode_create": "분해 보기",
"cmd.explode_delete": "분해 보기 삭제",
"cmd.explode_list": "분해 보기 목록",
"cmd.explode_show": "분해 보기 표시",
"cmd.expr_evaluate": "식 평가",
"cmd.face_delete": "면 삭제",
"cmd.file_export": "내보내기",
"cmd.file_insert_mesh": "메쉬 삽입",
"cmd.file_insert_step": "STEP 삽입",
"cmd.file_save_mesh": "메쉬로 저장",
"cmd.inspect_accessibility": "접근성 분석",
"cmd.inspect_center_of_mass": "무게중심",
"cmd.inspect_curvature_comb": "곡률빗 분석",
"cmd.inspect_curvature_map": "곡률맵 분석",
"cmd.inspect_draft": "블록각 분석",
"cmd.inspect_environment_map": "환경 맵 분석",
"cmd.inspect_interference": "간섭",
"cmd.inspect_isocurve": "등가곡선 분석",
"cmd.inspect_minimum_radius": "최소 반경 분석",
"cmd.inspect_zebra": "얼룩말줄무늬 분석",
"cmd.joint_as_built": "조립 상태 관절",
"cmd.joint_create": "관절",
"cmd.joint_delete": "관절 삭제",
"cmd.joint_drive": "관절 구동",
"cmd.joint_edit": "관절 편집",
"cmd.joint_limits": "관절 제한",
"cmd.joint_list": "관절 목록",
"cmd.joint_motion_link": "모션 링크",
"cmd.joint_origin": "관절 원점",
"cmd.joint_rigid_group": "강체 그룹",
"cmd.joint_solve": "관절 해석",
"cmd.material_assign": "물리 재질",
"cmd.material_create": "새 물리 재질",
"cmd.material_delete": "물리 재질 삭제",
"cmd.material_edit": "물리 재질 편집",
"cmd.material_list": "재질 목록",
"cmd.mesh_convert": "메쉬 변환",
"cmd.model_edges": "모서리 목록",
"cmd.model_faces": "면 목록",
"cmd.model_threads": "나사산 목록",
"cmd.motion_delete": "운동 연구 삭제",
"cmd.motion_export": "운동 연구 내보내기",
"cmd.motion_list": "운동 연구 목록",
"cmd.motion_play": "운동 연구 재생",
"cmd.motion_study": "운동 연구",
"cmd.occurrence_copy": "붙여넣기(인스턴스)",
"cmd.occurrence_ground": "고정",
"cmd.occurrence_move": "인스턴스 이동",
"cmd.occurrence_properties": "인스턴스 물리 속성",
"cmd.parameters_add": "사용자 매개 변수 추가",
"cmd.parameters_change": "매개 변수 변경",
"cmd.parameters_comment": "매개 변수에 설명 추가",
"cmd.parameters_complete": "식 완성",
"cmd.parameters_delete": "매개 변수 삭제",
"cmd.parameters_export": "매개 변수 내보내기",
"cmd.parameters_favorite": "즐겨찾는 매개 변수",
"cmd.parameters_graph": "매개 변수 그래프",
"cmd.parameters_import": "매개 변수 가져오기",
"cmd.parameters_list": "매개 변수 목록",
"cmd.parameters_rename": "매개 변수 이름 변경",
"cmd.parameters_users": "매개 변수 사용처",
"cmd.parts_fastener": "체결 부품 삽입",
"cmd.parts_insert": "부품 삽입",
"cmd.parts_library": "표준 부품",
"cmd.parts_resize": "부품 크기 변경",
"cmd.plastic_assign_rule": "플라스틱 규칙 지정",
"cmd.plastic_boss": "보스",
"cmd.plastic_lip": "리프",
"cmd.plastic_manage_rules": "플라스틱 규칙 관리",
"cmd.plastic_rest": "레스트",
"cmd.plastic_rules": "플라스틱 규칙 목록",
"cmd.plastic_snap_fit": "스냅핏",
"cmd.select_clear": "선택 지우기",
"cmd.sheet_convert": "판금으로 변환",
"cmd.sheet_export_bend_table": "벤딩 표 내보내기",
"cmd.sheet_export_dxf": "전개도를 DXF로 내보내기",
"cmd.sheet_flange": "플랜지",
"cmd.sheet_flat_pattern": "전개도 만들기",
"cmd.sheet_fold": "접기",
"cmd.sheet_hem": "헴",
"cmd.sheet_manage_rules": "판금 규칙",
"cmd.sheet_refold": "다시 접기",
"cmd.sheet_rules": "판금 규칙 목록",
"cmd.sheet_unfold": "펼치기",
"cmd.sketch_arc_center_point": "중심점 호",
"cmd.sketch_arc_tangent": "접선 호",
"cmd.sketch_arc_three_point": "3점 호",
"cmd.sketch_auto_constrain": "자동 구속",
"cmd.sketch_auto_constrain_from_datum": "기준으로 자동 구속",
"cmd.sketch_auto_project": "참조에 모서리 자동 투영",
"cmd.sketch_blend_curve": "블렌드 곡선",
"cmd.sketch_chain": "연속",
"cmd.sketch_chamfer_distance_angle": "거리와 각도 모따기",
"cmd.sketch_chamfer_equal_distance": "등거리 모따기",
"cmd.sketch_chamfer_two_distance": "두 거리 모따기",
"cmd.sketch_circle_center": "중심 지름 원",
"cmd.sketch_circle_three_point": "3점 원",
"cmd.sketch_circle_three_tangent": "3접선 원",
"cmd.sketch_circle_two_point": "2점 원",
"cmd.sketch_circle_two_tangent": "2접선 원",
"cmd.sketch_constrainer": "스케치 구속기",
"cmd.sketch_constraint_fix": "고정/고정 해제",
"cmd.sketch_constraint_midpoint": "중간점",
"cmd.sketch_construction": "일반/구성선",
"cmd.sketch_delete": "스케치 개체 삭제",
"cmd.sketch_dimension_text": "치수 텍스트 이동",
"cmd.sketch_drag": "스케치 기하 드래그",
"cmd.sketch_drag_end": "스케치 드래그 종료",
"cmd.sketch_draw_dimensioned": "치수가 있는 스케치 개체 그리기",
"cmd.sketch_edit": "스케치 편집",
"cmd.sketch_edit_text": "텍스트 편집",
"cmd.sketch_export_dxf": "DXF로 저장",
"cmd.sketch_finish_auto_constrain": "자동 구속으로 완료",
"cmd.sketch_fit_curves_to_section": "메시 단면에 곡선 맞추기",
"cmd.sketch_glyphs": "구속 기호",
"cmd.sketch_include_3d": "3D 기하 포함",
"cmd.sketch_insert_dxf": "DXF 삽입",
"cmd.sketch_insert_svg": "SVG 삽입",
"cmd.sketch_inspect": "스케치 검사",
"cmd.sketch_intersection_curve": "교차 곡선",
"cmd.sketch_isoparametric_curve": "등매개 곡선",
"cmd.sketch_line3d": "3D 선",
"cmd.sketch_line_midpoint": "중점 선",
"cmd.sketch_move": "스케치 개체 이동/복사",
"cmd.sketch_move3d": "3D 점 이동",
"cmd.sketch_move_point": "스케치 점 드래그",
"cmd.sketch_offset_break": "오프셋 연결 끊기",
"cmd.sketch_options": "스케치 팔레트 옵션",
"cmd.sketch_point3d": "3D 점",
"cmd.sketch_polygon_circumscribed": "외접 다각형",
"cmd.sketch_polygon_edge": "변 다각형",
"cmd.sketch_polygon_inscribed": "내접 다각형",
"cmd.sketch_project_to_surface": "곡면으로 투영",
"cmd.sketch_rectangle_center": "중심 사각형",
"cmd.sketch_rectangle_three_point": "3점 사각형",
"cmd.sketch_rectangle_two_point": "2점 사각형",
"cmd.sketch_redefine": "스케치 평면 재정의",
"cmd.sketch_slot_arc_center": "중심점 호 슬롯",
"cmd.sketch_slot_arc_three_point": "3점 호 슬롯",
"cmd.sketch_slot_center_point": "중심점 슬롯",
"cmd.sketch_slot_center_to_center": "중심에서 중심 슬롯",
"cmd.sketch_slot_overall": "전장 슬롯",
"cmd.sketch_snap": "스냅 추론",
"cmd.sketch_solve": "스케치 해석",
"cmd.sketch_spline3d": "3D 스플라인",
"cmd.sketch_spline_control_point": "제어점 스플라인",
"cmd.sketch_spline_control_point_5": "제어점 스플라인(5차)",
"cmd.sketch_spline_fit_point": "맞춤점 스플라인",
"cmd.sketch_spun_profile": "회전 프로파일",
"cmd.sketch_toggle_driven": "종속 치수 전환",
"cmd.solid_align": "정렬",
"cmd.solid_boundary_fill": "경계 채우기",
"cmd.solid_bounding_solid": "둘러싸는 솔리드",
"cmd.solid_emboss": "엠보스",
"cmd.solid_move": "이동/복사",
"cmd.solid_offset_face": "면 오프셋",
"cmd.solid_press_pull": "밀기/당기기",
"cmd.solid_remove": "제거",
"cmd.solid_replace_face": "면 교체",
"cmd.solid_rib": "리브",
"cmd.solid_split_face": "면 분할",
"cmd.solid_web": "웹",
"cmd.surface_patch": "패치",
"cmd.surface_stitch": "재봉",
"cmd.surface_thicken": "두께 주기",
"cmd.surface_extrude": "서피스 돌출",
"cmd.surface_revolve": "서피스 회전",
"cmd.surface_sweep": "서피스 스윕",
"cmd.surface_loft": "서피스 로프트",
"cmd.surface_ruled": "룰드 서피스",
"cmd.surface_boundary": "바운더리 서피스",
"cmd.surface_offset": "오프셋 서피스",
"cmd.surface_blend": "블렌드 서피스",
"cmd.surface_fillet": "서피스 필렛",
"cmd.surface_untrim": "트림 해제",
"cmd.surface_unstitch": "스티치 해제",
"cmd.surface_reverse_normal": "법선 반전",
"cmd.inspect_continuity": "연속성 분석",
"cmd.timeline_compute_all": "모두 다시 계산",
"cmd.timeline_copy": "특성 복사",
"cmd.timeline_dependents": "특성 종속 항목",
"cmd.timeline_inspect": "특성 검사",
"cmd.timeline_paste": "특성 붙여넣기",
"cmd.timeline_redefine": "특성 편집",
"cmd.timeline_rename": "이름 변경",
"cmd.timeline_reorder": "특성 순서 변경",
"cmd.timeline_roll_to": "이력 마커 이동",
"cmd.timeline_rollback": "타임라인 마커 이동",
"cmd.timeline_suppress": "특성 비활성화",
"cmd.view_apply": "이름 있는 보기 복원",
"cmd.view_delete": "이름 있는 보기 삭제",
"cmd.view_list": "이름 있는 보기 목록",
"cmd.view_rename": "이름 있는 보기 이름 변경",
"cmd.view_save": "새 이름 있는 보기",
"ui.export_ellipsis_keymap": "내보내기…",
"ui.heading_configure": "구성",
"ui.heading_constraints": "구속 조건",
"ui.heading_utilities": "유틸리티",
"BROWSER": "브라우저",
"Drive Joint": "조인트 구동",
"Edit Profile Sketch": "윤곽 스케치 편집",
"Find in Browser": "브라우저에서 찾기",
"Report a bug — review a screenshot and open a GitHub draft": "버그 신고 — 스크린샷을 검토하고 GitHub 초안 열기",
"Reset Toolbar": "도구 모음 재설정",
"Select bodies (a face picks its body)": "바디 선택(면을 클릭하면 해당 바디 선택)",
"Selection filter": "선택 필터",
"Revert Position": "위치 복원",
"Construction": "구성선",
"Faces": "면",
"Edges": "모서리",
"Vertices": "꼭짓점",
"Sketch geometry and profiles": "스케치 기하 및 프로파일",
"Sketch curves": "스케치 곡선",
"Sketch points": "스케치 점",
"Sketch dimensions": "스케치 치수",
"Sketch constraints": "스케치 구속 조건",
"Flat": "플랫",
"cmd.render_backends": "렌더링 백엔드",
"cmd.view_navigate": "보기 탐색",
"timeline.group": "새 특성 폴더",
"timeline.ungroup": "특성 폴더 제거",
"timeline.rename_group": "특성 폴더 이름 변경",
"timeline.move_to_group": "특성을 폴더로 이동",
"timeline.move_many": "타임라인 항목 이동",
"timeline.reorder_many": "특성 순서 변경",
"ui.show_construction_geometries": "구성 형상",
"ui.total_angle": "전진 각도",
"sketch_text.builtin_font": "{family}(내장)",
"sketch_text.font_search": "글꼴 검색",
"sketch_text.loading_fonts": "설치된 글꼴을 찾는 중…",
"sketch_text.use_font_file": "글꼴 파일 {path} 사용",
"sketch_text.bold": "굵게",
"sketch_text.italic": "기울임꼴",
"sketch_text.align_left": "왼쪽",
"sketch_text.align_center": "가운데",
"sketch_text.align_right": "오른쪽",
"sketch_text.path_hint": "텍스트가 따라갈 스케치 곡선: 여기를 클릭한 다음 곡선을 클릭",
"sketch_text.path_clear": "곡선에서 텍스트 떼기",
"sketch_text.on_path": "텍스트가 경로를 따릅니다: 위치는 곡선이 정합니다"
}
+961
View File
@@ -0,0 +1,961 @@
{
"browser.body_name": "实体{number}",
"browser.component_name": "组件{number}",
"browser.parameters": "参数 ({count})",
"browser.precision": "精度: {precision}",
"browser.sketch_name": "草图{number}",
"browser.units": "单位: {units}",
"cmd.appearance_assign": "外观",
"cmd.appearance_library": "外观库",
"cmd.appearance_list": "列出外观",
"cmd.body_rename": "重命名实体",
"cmd.browser_group": "新建组",
"cmd.browser_move": "移动浏览器项目",
"cmd.browser_order": "排列浏览器项目",
"cmd.browser_rename_group": "重命名组",
"cmd.browser_ungroup": "取消编组",
"cmd.browser_visibility": "显示/隐藏",
"cmd.canvas_calibrate": "校准画布",
"cmd.canvas_decal": "贴花",
"cmd.canvas_delete": "删除画布",
"cmd.canvas_edit": "编辑画布",
"cmd.canvas_insert": "画布",
"cmd.component_activate": "激活组件",
"cmd.component_break_link": "断开链接",
"cmd.component_capture_position": "捕获位置",
"cmd.component_create": "新建组件",
"cmd.component_delete": "删除组件",
"cmd.component_from_bodies": "由实体创建组件",
"cmd.component_insert_design": "插入设计",
"cmd.component_links": "列出链接",
"cmd.component_list": "列出组件",
"cmd.component_move_bodies": "将实体移至组件",
"cmd.component_move_sketches": "将草图移至组件",
"cmd.component_paste_new": "粘贴为新对象",
"cmd.component_properties": "组件物理属性",
"cmd.component_rename": "重命名组件",
"cmd.component_revert_position": "恢复位置",
"cmd.component_update_links": "更新链接",
"cmd.config_activate": "激活配置",
"cmd.config_column": "配置列",
"cmd.config_configure": "配置",
"cmd.config_preview": "预览配置",
"cmd.config_row": "配置行",
"cmd.config_rule": "配置规则",
"cmd.config_rules": "配置规则",
"cmd.config_table": "显示配置表",
"cmd.construct_axis_edge": "过边的轴",
"cmd.construct_axis_normal_to_face": "垂直于面的轴",
"cmd.construct_axis_through_cylinder": "过圆柱/圆锥/圆环的轴",
"cmd.construct_axis_two_planes": "过两个平面的轴",
"cmd.construct_axis_two_points": "过两点的轴",
"cmd.construct_plane_along_path": "沿路径的平面",
"cmd.construct_plane_midplane": "中平面",
"cmd.construct_plane_perpendicular": "垂直平面",
"cmd.construct_plane_tangent": "相切平面",
"cmd.construct_plane_three_points": "过三点的平面",
"cmd.construct_plane_two_edges": "过两条边的平面",
"cmd.construct_point_along_path": "沿路径的点",
"cmd.construct_point_center": "圆/球/圆环中心的点",
"cmd.construct_point_edge_plane": "边与平面交点",
"cmd.construct_point_three_planes": "过三个平面的点",
"cmd.construct_point_two_edges": "过两条边的点",
"cmd.construct_point_vertex": "顶点处的点",
"cmd.contact_check": "检查接触",
"cmd.contact_create": "新建接触集",
"cmd.contact_disable_all": "禁用接触",
"cmd.contact_edit": "编辑接触集",
"cmd.contact_enable_all": "启用全部接触",
"cmd.contact_enable_sets": "启用接触集",
"cmd.contact_list": "列出接触集",
"cmd.diagnostics_crash_report": "崩溃报告",
"cmd.diagnostics_crash_reports": "崩溃报告",
"cmd.diagnostics_crash_reports_clear": "删除崩溃报告",
"cmd.diagnostics_crash_test": "崩溃测试",
"cmd.doc_recover": "恢复设计",
"cmd.doc_recovery_discard": "丢弃已恢复的设计",
"cmd.doc_recovery_list": "可恢复的设计",
"cmd.document_inspect": "检查设计",
"cmd.document_units": "更改设计单位",
"cmd.edit_checkpoint": "检查点",
"cmd.edit_restore_checkpoint": "恢复检查点",
"cmd.engine_commands": "列出命令",
"cmd.explode_create": "爆炸图",
"cmd.explode_delete": "删除爆炸图",
"cmd.explode_list": "列出爆炸图",
"cmd.explode_show": "显示爆炸图",
"cmd.expr_evaluate": "计算表达式",
"cmd.face_delete": "删除面",
"cmd.file_export": "导出",
"cmd.file_insert_mesh": "插入网格",
"cmd.file_insert_step": "插入 STEP",
"cmd.file_save_mesh": "另存为网格",
"cmd.inspect_accessibility": "可达性分析",
"cmd.inspect_center_of_mass": "质心",
"cmd.inspect_curvature_comb": "曲率梳分析",
"cmd.inspect_curvature_map": "曲率图分析",
"cmd.inspect_draft": "拔模分析",
"cmd.inspect_environment_map": "环境贴图分析",
"cmd.inspect_interference": "干涉",
"cmd.inspect_isocurve": "等参线分析",
"cmd.inspect_minimum_radius": "最小半径分析",
"cmd.inspect_zebra": "斑马纹分析",
"cmd.joint_as_built": "现状接头",
"cmd.joint_create": "接头",
"cmd.joint_delete": "删除接头",
"cmd.joint_drive": "驱动接头",
"cmd.joint_edit": "编辑接头",
"cmd.joint_limits": "接头限制",
"cmd.joint_list": "列出接头",
"cmd.joint_motion_link": "运动关联",
"cmd.joint_origin": "接头原点",
"cmd.joint_rigid_group": "刚性组",
"cmd.joint_solve": "解算接头",
"cmd.material_assign": "物理材质",
"cmd.material_create": "新建物理材质",
"cmd.material_delete": "删除物理材质",
"cmd.material_edit": "编辑物理材质",
"cmd.material_list": "列出材质",
"cmd.mesh_convert": "转换网格",
"cmd.model_edges": "列出边",
"cmd.model_faces": "列出面",
"cmd.model_threads": "列出螺纹",
"cmd.motion_delete": "删除运动研究",
"cmd.motion_export": "导出运动研究",
"cmd.motion_list": "列出运动研究",
"cmd.motion_play": "播放运动研究",
"cmd.motion_study": "运动研究",
"cmd.occurrence_copy": "粘贴(实例)",
"cmd.occurrence_ground": "固定",
"cmd.occurrence_move": "移动实例",
"cmd.occurrence_properties": "实例物理属性",
"cmd.parameters_add": "添加用户参数",
"cmd.parameters_change": "更改参数",
"cmd.parameters_comment": "注释参数",
"cmd.parameters_complete": "补全表达式",
"cmd.parameters_delete": "删除参数",
"cmd.parameters_export": "导出参数",
"cmd.parameters_favorite": "收藏参数",
"cmd.parameters_graph": "参数关系图",
"cmd.parameters_import": "导入参数",
"cmd.parameters_list": "列出参数",
"cmd.parameters_rename": "重命名参数",
"cmd.parameters_users": "参数使用者",
"cmd.parts_fastener": "插入紧固件",
"cmd.parts_insert": "插入零件",
"cmd.parts_library": "标准零件",
"cmd.parts_resize": "更改零件尺寸",
"cmd.plastic_assign_rule": "指定塑料规则",
"cmd.plastic_boss": "凸台",
"cmd.plastic_lip": "凸缘",
"cmd.plastic_manage_rules": "管理塑料规则",
"cmd.plastic_rest": "垫块",
"cmd.plastic_rules": "列出塑料规则",
"cmd.plastic_snap_fit": "卡扣",
"cmd.select_clear": "清除选择",
"cmd.sheet_convert": "转换为钣金",
"cmd.sheet_export_bend_table": "导出折弯表",
"cmd.sheet_export_dxf": "将展开图导出为 DXF",
"cmd.sheet_flange": "法兰",
"cmd.sheet_flat_pattern": "创建展开图",
"cmd.sheet_fold": "折叠",
"cmd.sheet_hem": "卷边",
"cmd.sheet_manage_rules": "钣金规则",
"cmd.sheet_refold": "重新折弯",
"cmd.sheet_rules": "列出钣金规则",
"cmd.sheet_unfold": "展开",
"cmd.sketch_arc_center_point": "中心点圆弧",
"cmd.sketch_arc_tangent": "相切圆弧",
"cmd.sketch_arc_three_point": "三点圆弧",
"cmd.sketch_auto_constrain": "自动约束",
"cmd.sketch_auto_constrain_from_datum": "基于基准自动约束",
"cmd.sketch_auto_project": "自动投影边到参考",
"cmd.sketch_blend_curve": "混合曲线",
"cmd.sketch_chain": "连续链",
"cmd.sketch_chamfer_distance_angle": "距离和角度倒角",
"cmd.sketch_chamfer_equal_distance": "等距倒角",
"cmd.sketch_chamfer_two_distance": "双距离倒角",
"cmd.sketch_circle_center": "圆心直径圆",
"cmd.sketch_circle_three_point": "三点圆",
"cmd.sketch_circle_three_tangent": "三切线圆",
"cmd.sketch_circle_two_point": "两点圆",
"cmd.sketch_circle_two_tangent": "两切线圆",
"cmd.sketch_constrainer": "草图约束器",
"cmd.sketch_constraint_fix": "固定/解除固定",
"cmd.sketch_constraint_midpoint": "中点",
"cmd.sketch_construction": "普通/构造",
"cmd.sketch_delete": "删除草图图元",
"cmd.sketch_dimension_text": "移动尺寸文字",
"cmd.sketch_drag": "拖动草图几何",
"cmd.sketch_drag_end": "结束草图拖动",
"cmd.sketch_draw_dimensioned": "绘制带尺寸的草图图元",
"cmd.sketch_edit": "编辑草图",
"cmd.sketch_edit_text": "编辑文字",
"cmd.sketch_export_dxf": "另存为 DXF",
"cmd.sketch_finish_auto_constrain": "使用自动约束完成",
"cmd.sketch_fit_curves_to_section": "将曲线拟合到网格截面",
"cmd.sketch_glyphs": "约束符号",
"cmd.sketch_include_3d": "包含三维几何",
"cmd.sketch_insert_dxf": "插入 DXF",
"cmd.sketch_insert_svg": "插入 SVG",
"cmd.sketch_inspect": "检查草图",
"cmd.sketch_intersection_curve": "相交曲线",
"cmd.sketch_isoparametric_curve": "等参数曲线",
"cmd.sketch_line3d": "三维直线",
"cmd.sketch_line_midpoint": "中点线",
"cmd.sketch_move": "移动/复制草图图元",
"cmd.sketch_move3d": "移动三维点",
"cmd.sketch_move_point": "拖动草图点",
"cmd.sketch_offset_break": "断开偏移关联",
"cmd.sketch_options": "草图选项板选项",
"cmd.sketch_point3d": "三维点",
"cmd.sketch_polygon_circumscribed": "外切多边形",
"cmd.sketch_polygon_edge": "边长多边形",
"cmd.sketch_polygon_inscribed": "内接多边形",
"cmd.sketch_project_to_surface": "投影到曲面",
"cmd.sketch_rectangle_center": "中心矩形",
"cmd.sketch_rectangle_rounded": "圆角矩形",
"cmd.sketch_rectangle_three_point": "三点矩形",
"cmd.sketch_rectangle_two_point": "两点矩形",
"cmd.sketch_redefine": "重定义草图平面",
"cmd.sketch_slot_arc_center": "中心点圆弧槽",
"cmd.sketch_slot_arc_three_point": "三点圆弧槽",
"cmd.sketch_slot_center_point": "中心点槽",
"cmd.sketch_slot_center_to_center": "中心距槽",
"cmd.sketch_slot_overall": "全长槽",
"cmd.sketch_snap": "捕捉推断",
"cmd.sketch_solve": "解算草图",
"cmd.sketch_spline3d": "三维样条曲线",
"cmd.sketch_spline_control_point": "控制点样条曲线",
"cmd.sketch_spline_control_point_5": "控制点样条曲线(5 次)",
"cmd.sketch_spline_fit_point": "拟合点样条曲线",
"cmd.sketch_spun_profile": "旋转轮廓",
"cmd.sketch_toggle_driven": "切换被驱动尺寸",
"cmd.solid_align": "对齐",
"cmd.solid_boundary_fill": "边界填充",
"cmd.solid_bounding_solid": "包围实体",
"cmd.solid_emboss": "压印",
"cmd.solid_move": "移动/复制",
"cmd.solid_offset_face": "偏移面",
"cmd.solid_press_pull": "按压拖动",
"cmd.solid_remove": "移除",
"cmd.solid_replace_face": "替换面",
"cmd.solid_rib": "筋板",
"cmd.solid_split_face": "拆分面",
"cmd.solid_web": "网格筋",
"cmd.surface_patch": "补面",
"cmd.surface_stitch": "缝合",
"cmd.surface_thicken": "加厚",
"cmd.surface_extrude": "曲面拉伸",
"cmd.surface_revolve": "曲面旋转",
"cmd.surface_sweep": "曲面扫掠",
"cmd.surface_loft": "曲面放样",
"cmd.surface_ruled": "直纹曲面",
"cmd.surface_boundary": "边界曲面",
"cmd.surface_offset": "偏移曲面",
"cmd.surface_blend": "混合曲面",
"cmd.surface_fillet": "曲面圆角",
"cmd.surface_untrim": "取消修剪",
"cmd.surface_unstitch": "取消缝合",
"cmd.surface_reverse_normal": "反转法向",
"cmd.inspect_continuity": "连续性分析",
"cmd.timeline_compute_all": "全部重新計算",
"cmd.timeline_copy": "复制特征",
"cmd.timeline_dependents": "特征依存项",
"cmd.timeline_inspect": "检查特征",
"cmd.timeline_paste": "粘贴特征",
"cmd.timeline_redefine": "编辑特征",
"cmd.timeline_rename": "重命名",
"cmd.timeline_reorder": "重排特征",
"cmd.timeline_roll_to": "滚动历史标记",
"cmd.timeline_rollback": "移动时间线标记",
"cmd.timeline_suppress": "抑制特征",
"cmd.view_apply": "还原命名视图",
"cmd.view_delete": "删除命名视图",
"cmd.view_list": "列出命名视图",
"cmd.view_rename": "重命名命名视图",
"cmd.view_save": "新建命名视图",
"dialog.custom_axis": "↖ 自定义轴",
"dialog.edit_title": "编辑 {title}",
"dialog.select": "↖ 选择",
"dialog.selected_count": "↖ 已选择 {count} 个",
"dialog.step": "步骤",
"dialog.selection_step": "第 {step} 步,共 {total} 步:选择{input}",
"dialog.selection_guidance": "选择输入项进行修改。点击已选对象可取消选择;按住 Ctrl/Cmd 点击也可切换选择。检查预览后,再按“确定”。",
"document.save_changes": "保存对 {name} 的更改?",
"document.unsaved_changes": "{name} 有未保存的更改。如果不保存,将丢失这些更改。",
"settings.msaa_applies_on_restart": "抗锯齿将在下次启动 SolidCraft 时生效",
"settings.msaa_reset": "{setting}: {requested}× 不受此图形设备支持;已重置为 {reset}× (支持: {supported})。",
"settings.msaa_unsupported": "此图形设备不支持",
"settings.reset_intro": "这些设置在此计算机上无法正常工作,会导致 SolidCraft 无法启动,因此已更改:",
"settings.reset_title": "设置已更改",
"sketch.degrees_of_freedom": "{count} 个自由度",
"sketch.draw_curves": "绘制为 {linetype}",
"sketch.make_selection": "将选择转换为 {linetype}",
"ui.1_degree_of_freedom": "1 个自由度",
"ui.3d_mouse_speed": "3D 鼠标速度",
"ui.3d_mouse_windows": "3D 鼠标(Windows)",
"ui.3d_sketch": "3D 草图",
"ui.about": "关于",
"ui.activate": "激活",
"ui.active_component": "活动组件",
"ui.agent_cursor": "代理光标",
"ui.agent_cursor_speed": "代理光标速度",
"ui.all": "全部",
"ui.angle": "角度",
"ui.angle_decimals": "角度小数位",
"ui.animate_view_changes": "视图变化动画",
"ui.animation": "动画",
"ui.animation_speed": "动画速度",
"ui.anti_aliasing": "抗锯齿",
"ui.apply": "应用",
"ui.arc": "圆弧",
"ui.assemble": "装配",
"ui.autosave_every": "自动保存间隔",
"ui.axes": "轴",
"ui.axis": "轴",
"ui.base_point": "基点",
"ui.bodies": "实体",
"ui.box": "长方体",
"ui.break": "打断",
"ui.cancel": "取消",
"ui.canvases": "画布",
"ui.center": "中心",
"ui.centerline": "中心线",
"ui.chain": "链",
"ui.chamfer": "倒角",
"ui.chamfer_type": "倒角类型",
"ui.change_workspace": "更改工作区",
"ui.chord_length": "弦长",
"ui.curvature_g2": "曲率连续 (G2)",
"ui.equivalent_radius": "等效半径",
"ui.circle": "圆",
"ui.circular_pattern": "圆形阵列",
"ui.clear": "清除",
"ui.clear_recent": "清除最近",
"ui.clear_the_selection": "清除选择",
"ui.click_selects_whole_bodies": "单击选择整个实体",
"ui.click_then_press_key": "单击,然后按新按键",
"ui.close": "关闭",
"ui.coil": "螺旋",
"ui.coincident": "重合",
"ui.collapse": "折叠",
"ui.collinear": "共线",
"ui.combine": "组合",
"ui.coming_later": "即将推出",
"ui.concentric": "同心",
"ui.configurations": "配置",
"ui.conic_curve": "圆锥曲线",
"ui.constant": "恒定",
"ui.constraints_conflict": "约束冲突",
"ui.construct": "构造",
"ui.construction": "构造",
"ui.context_menu": "上下文菜单",
"ui.copy": "复制",
"ui.corners": "角点",
"ui.corner": "角",
"ui.counterbore": "沉头孔",
"ui.countersink": "锥形沉孔",
"ui.create": "创建",
"ui.create_copy": "创建副本",
"ui.create_sketch": "创建草图",
"ui.current": "当前",
"ui.curvature": "曲率",
"ui.curves": "曲线",
"ui.cut": "剪切",
"ui.cylinder": "圆柱体",
"ui.dark": "深色",
"ui.default_modelling_orientation": "默认建模方向",
"ui.degrees_of_freedom": "自由度",
"ui.delete": "删除",
"ui.depth": "深度",
"ui.design": "设计",
"ui.diameter": "直径",
"ui.dimension": "尺寸",
"ui.direction": "方向",
"ui.distance": "距离",
"ui.distance_1": "距离 1",
"ui.distance_2": "距离 2",
"ui.document_settings": "文档设置",
"ui.don_t_save": "不保存",
"ui.draft": "拔模",
"ui.drawing": "绘图",
"ui.drill_point": "钻尖",
"ui.edit": "编辑",
"ui.ellipse": "椭圆",
"ui.end_radius": "结束半径",
"ui.entities": "实体",
"ui.equal": "相等",
"ui.expand": "展开",
"ui.export_ellipsis": "导出...",
"ui.export_ellipsis_keymap": "导出…",
"ui.export_keymap_tip": "将按键、S 框收藏和工具栏导出为文件",
"ui.extend": "延伸",
"ui.extent_type": "延伸类型",
"ui.extrude": "拉伸",
"ui.factor": "系数",
"ui.feature_history": "特征历史",
"ui.feature_options": "特征选项",
"ui.features": "特征",
"ui.file": "文件",
"ui.fillet": "圆角",
"ui.filter_commands_or_keys": "筛选命令或按键",
"ui.finish_sketch": "完成草图",
"ui.fix_unfix": "固定/取消固定",
"ui.flat": "平面",
"ui.flip": "翻转",
"ui.front": "前视",
"ui.fully_constrained": "完全约束",
"ui.general": "常规",
"ui.graphics": "图形",
"ui.ground_shadow": "地面阴影",
"ui.heading_add_ins": "加载项",
"ui.heading_assemble": "装配",
"ui.heading_box": "长方体",
"ui.heading_chamfer": "倒角",
"ui.heading_circular_pattern": "圆形阵列",
"ui.heading_coil": "螺旋",
"ui.heading_combine": "组合",
"ui.heading_configure": "配置",
"ui.heading_constraints": "约束",
"ui.heading_construct": "构造",
"ui.heading_create": "创建",
"ui.heading_create_sketch": "创建草图",
"ui.heading_cylinder": "圆柱体",
"ui.heading_design": "设计",
"ui.heading_draft": "拔模",
"ui.heading_edit": "编辑",
"ui.heading_extrude": "拉伸",
"ui.heading_fillet": "圆角",
"ui.heading_finish_sketch": "完成草图",
"ui.heading_front": "前视",
"ui.heading_hole": "孔",
"ui.heading_home": "主页",
"ui.heading_insert": "插入",
"ui.heading_inspect": "检查",
"ui.heading_loft": "放样",
"ui.heading_make": "制作",
"ui.heading_measure": "测量",
"ui.heading_mesh": "网格",
"ui.heading_mirror": "镜像",
"ui.heading_modify": "修改",
"ui.heading_move": "移动",
"ui.heading_offset": "偏移",
"ui.heading_move_copy": "移动/复制",
"ui.heading_offset_plane": "偏移平面",
"ui.heading_ok": "确定",
"ui.heading_parameters": "参数",
"ui.heading_pattern_on_path": "路径阵列",
"ui.heading_pipe": "管道",
"ui.heading_plane_at_angle": "角度平面",
"ui.heading_plastic": "塑料",
"ui.heading_prepare": "准备",
"ui.heading_rectangular_pattern": "矩形阵列",
"ui.heading_revolve": "旋转",
"ui.heading_right": "右视",
"ui.heading_scale": "缩放",
"ui.heading_section_analysis": "截面分析",
"ui.heading_select": "选择",
"ui.heading_sheet_metal": "钣金",
"ui.heading_shell": "抽壳",
"ui.heading_sketch": "草图",
"ui.heading_sketch_palette": "草图面板",
"ui.heading_sketch_scale": "草图缩放",
"ui.heading_solid": "实体",
"ui.heading_sphere": "球体",
"ui.heading_split_body": "分割实体",
"ui.heading_surface": "曲面",
"ui.heading_sweep": "扫掠",
"ui.heading_thread": "螺纹",
"ui.heading_top": "上视",
"ui.heading_torus": "圆环体",
"ui.heading_utilities": "工具",
"ui.heading_utility": "实用工具",
"ui.help": "帮助",
"ui.hide": "隐藏",
"ui.hole": "孔",
"ui.hole_tap_type": "孔攻丝类型",
"ui.hole_type": "孔类型",
"ui.home": "主页",
"ui.horizontal_vertical": "水平/垂直",
"ui.import_ellipsis": "导入…",
"ui.insert": "插入",
"ui.inspect": "检查",
"ui.intersect": "相交",
"ui.join": "连接",
"ui.joints": "关节",
"ui.keyboard_shortcuts": "键盘快捷键",
"ui.language": "语言",
"ui.length_decimals": "长度小数位",
"ui.light": "浅色",
"ui.line": "直线",
"ui.linetype": "线型",
"ui.loft": "放样",
"ui.look_at": "朝向",
"ui.look_at_anim_desc": "轴视图、注视、主视图与适合窗口",
"ui.manufacture": "制造",
"ui.measure": "测量",
"ui.midpoint": "中点",
"ui.mirror": "镜像",
"ui.mirror_line": "镜像线",
"ui.modify": "修改",
"ui.move": "移动",
"ui.multiple": "多个",
"ui.named_views": "命名视图",
"ui.navigation": "导航",
"ui.needs_design": "{command}: 请先打开一个设计",
"ui.new_body": "新建实体",
"ui.new_design": "新建设计",
"ui.normal": "法向",
"ui.object_type": "对象类型",
"ui.objects": "对象",
"ui.offer_to_stand_up_y_up_files": "提供站立 Y-up 文件",
"ui.offset": "偏移",
"ui.offset_plane": "偏移平面",
"ui.one_side": "单侧",
"ui.open": "打开",
"ui.open_ellipsis": "打开...",
"ui.open_preferences": "打开首选项",
"ui.open_recent": "打开最近",
"ui.operation": "操作",
"ui.options": "选项",
"ui.orbit_around": "环绕",
"ui.origin": "原点",
"ui.palette_points": "点",
"ui.palette_profile": "轮廓",
"ui.pan_zoom_orbit": "平移、缩放、环绕",
"ui.parallel": "平行",
"ui.parameters": "参数",
"ui.paste": "粘贴",
"ui.path": "路径",
"ui.pattern_on_path": "路径阵列",
"ui.perpendicular": "垂直",
"ui.perspective_view": "透视图",
"ui.pipe": "管道",
"ui.placement": "放置",
"ui.plane": "平面",
"ui.plane_at_angle": "角度平面",
"ui.point": "点",
"ui.point_under_the_cursor": "光标下的点",
"ui.points": "点",
"ui.polygon": "多边形",
"ui.preferences": "首选项",
"ui.press_a_key": "按一个键…",
"ui.profile": "轮廓",
"ui.profile_plane": "轮廓平面",
"ui.profiles": "轮廓",
"ui.project": "投影",
"ui.project_face_edges_sketch": "将面的边投影到新草图",
"ui.quantity_2": "数量 2",
"ui.quantity": "数量",
"ui.quit": "退出",
"ui.radial_context_menu": "环形上下文菜单",
"ui.radius": "半径",
"ui.radius_type": "半径类型",
"ui.rectangle": "矩形",
"ui.rectangular_pattern": "矩形阵列",
"ui.redo": "重做",
"ui.render": "渲染",
"ui.renderer": "渲染器",
"ui.reset": "重置",
"ui.reset_all": "全部重置",
"ui.reset_the_settings_on_this_page": "重置此页面上的设置",
"ui.restore_defaults": "恢复默认值",
"ui.reverse_zoom_direction": "反转缩放方向",
"ui.revolve": "旋转",
"ui.right": "右视",
"ui.save": "保存",
"ui.save_as": "另存为",
"ui.save_as_ellipsis": "另存为...",
"ui.scale": "缩放",
"ui.section_analysis": "截面分析",
"ui.select": "选择",
"ui.select_other_hint": "“选择其他”:在模型上按住左键(或右键单击 › 选择其他…)列出光标下的所有对象;在同一位置再次单击可选择该处的下一个对象。",
"ui.selection": "选择",
"ui.shell": "抽壳",
"ui.show": "显示",
"ui.show_constraints": "约束",
"ui.show_dimensions": "尺寸",
"ui.show_points": "显示点",
"ui.show_profile": "显示轮廓",
"ui.show_projected_geometries": "投影几何",
"ui.simple": "简单",
"ui.simulation": "仿真",
"ui.single": "单个",
"ui.sketch": "草图",
"ui.sketch_dimension": "草图尺寸",
"ui.sketch_grid": "草图网格",
"ui.sketch_scale": "草图比例",
"ui.sketch_status": "草图状态",
"ui.sketches": "草图",
"ui.slice": "切片",
"ui.slot": "槽",
"ui.smooth": "平滑",
"ui.smooth_wheel_zoom": "平滑滚轮缩放",
"ui.smooth_wheel_zoom_tip": "关闭后每个滚轮事件立即生效,不做平滑延迟",
"ui.snap": "捕捉",
"ui.space_mouse_hint": "同时移动和旋转视图;草图中仅支持平移和缩放。",
"ui.space_mouse_navigate": "使用 SpaceNavigator / SpaceMouse 导航",
"ui.spacing_2": "间距 2",
"ui.spacing": "间距",
"ui.sphere": "球体",
"ui.spline": "样条曲线",
"ui.split_body": "分割实体",
"ui.stand_up_tip": "当 Z 朝上的 STEP 或 IGES 文件打开后,提示绕 X 轴旋转四分之一周",
"ui.start": "开始",
"ui.start_radius": "起始半径",
"ui.sweep": "扫掠",
"ui.symmetric": "对称",
"ui.symmetry": "对称",
"ui.system": "系统",
"ui.tangent": "相切",
"ui.taper_angle": "锥角",
"ui.tapped": "攻丝",
"ui.text": "文本",
"ui.theme": "主题",
"ui.thickness": "厚度",
"ui.thread": "螺纹",
"ui.timeline_bottom": "底部时间轴",
"ui.timeline_left_note": "左侧布局下,回退条显示在零件和场景浏览器的上方。",
"ui.timeline_left_sidebar": "左侧边栏",
"ui.tip_angle": "尖端角度",
"ui.to_object": "到对象",
"ui.top": "上视",
"ui.torus": "圆环体",
"ui.trackpad_pan_tip": "触控板手势用于平移(关闭:像滚轮一样缩放);鼠标滚轮缩放、拇指滚轮横向平移、双指捏合始终缩放",
"ui.trim": "修剪",
"ui.two_finger_scroll_pans": "双指滚动平移",
"ui.two_sides": "双侧",
"ui.ui_scale": "界面缩放",
"ui.undo": "撤销",
"ui.units": "单位",
"ui.units_of_new_designs": "新设计的单位",
"ui.untitled": "无标题",
"ui.up_axis_note": "视图中朝上的轴(轴视图、主视图、注视)。设计本身不变。",
"ui.variable": "变量",
"ui.view_centre": "视图中心",
"ui.visual_shaded_hidden_edges": "带隐藏边的着色",
"ui.visual_shaded_with_edges": "带边着色",
"ui.visual_style": "视觉样式",
"ui.visual_wireframe": "线框",
"ui.workspace": "工作区",
"ui.y_distance": "Y 距离",
"ui.x_distance": "X 距离",
"ui.y_up": "Y 朝上",
"ui.z_up": "Z 朝上",
"ui.zoom_to_cursor_tip": "滚轮向光标所在点缩放(关闭:向视图中心缩放)",
"ui.zoom_to_the_cursor": "缩放到光标",
"A new configuration, a copy of the active one": "新建配置,为当前配置的副本",
"About SolidCraft": "关于 SolidCraft",
"Active": "活动",
"Active rule": "活动规则",
"Add": "添加",
"Add Key": "添加按键",
"Add Mid Radius": "添加中间半径",
"Add Row": "添加行",
"All Sketch Points": "所有草图点",
"All open designs": "所有打开的设计",
"Allowed expressions, separated by semicolons": "允许的表达式,以分号分隔",
"Also deletes": "同时删除",
"Angle 2": "角度 2",
"Animate": "动画",
"Appearance name": "外观名称",
"Applied Radius": "应用半径",
"Apply To": "应用于",
"Area": "面积",
"Attachment": "附着",
"BROWSER": "浏览器",
"Bend Line Position": "弯折线位置",
"Bend Position": "折弯位置",
"Bend Radius": "折弯半径",
"Bends": "折弯",
"Body": "实体",
"Bounding box": "边界框",
"CSV or JSON: updates existing parameters, adds new ones": "CSV 或 JSON:更新现有参数、添加新参数",
"Capture All Positions": "捕获所有位置",
"Catch Direction": "卡扣方向",
"Cells": "区域",
"Center X": "中心 X",
"Center Y": "中心 Y",
"Center of mass": "质心",
"Chamfer Type": "倒角类型",
"Check collisions": "检查碰撞",
"Checked": "已检查",
"Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "选择一个新平面。草图几何和尺寸在其二维坐标中保持不变。",
"Close draft": "关闭草稿",
"Collisions": "碰撞",
"Colour": "颜色",
"Component1": "组件1",
"Configuration": "配置",
"Configuration rules": "配置规则",
"Contact": "接触",
"Copy all licences": "复制所有许可证",
"Copy diagnostic information": "复制诊断信息",
"Copy report": "复制报告",
"Corner Relief Shape": "拐角释放形状",
"Counterbore depth": "沉孔深度",
"Countersink angle": "沉头角度",
"Create Issue — review draft": "创建 Issue——审阅草稿",
"Customize Toolbar": "自定义工具栏",
"Delete all": "全部删除",
"Delete rule": "删除规则",
"Delete the row": "删除此行",
"Delete the set": "删除此集合",
"Delete this parameter": "删除此参数",
"Density": "密度",
"Designs you open or save appear here.": "你打开或保存的设计会显示在此处。",
"Determined by To Object. Choose Angle to edit the angular extent.": "由“到对象”确定。选择“角度”可编辑角度范围。",
"Discard": "放弃",
"Double-click to rename": "双击重命名",
"Drive Joint": "驱动接头",
"Edit Profile Sketch": "编辑轮廓草图",
"Enable rule": "启用规则",
"Enabled": "已启用",
"Explode": "爆炸",
"Extents": "范围",
"Face attachment lost": "面附着丢失",
"Faces / edges / vertices": "面 / 边 / 顶点",
"Favourite": "收藏",
"Files from many CAD programs have Y up, so they open lying on their back here (Z up).": "许多 CAD 程序的文件以 Y 为上轴,因此在此处打开时会平躺(Z 朝上)。",
"Filter": "筛选",
"Find in Browser": "在浏览器中查找",
"Flat size": "平板尺寸",
"Follow camera (Look At sketches)": "跟随相机(注视草图)",
"Font": "字体",
"Full Length": "全长",
"Gap": "间隙",
"Grid": "网格",
"Height": "高度",
"Home view": "主视图",
"In This Design": "在此设计中",
"Interferes": "发生干涉",
"Issue title": "Issue 标题",
"Joint Origin1": "接头原点1",
"Keep Tools": "保留工具",
"Later": "稍后",
"Leave it": "保持原样",
"Legacy face attachment. Rebinding to a certified face creates an exact attachment.": "旧版面附着。重新绑定到经验证的面可创建精确附着。",
"Length": "长度",
"Length units": "长度单位",
"Library": "零件库",
"Loop": "循环",
"Margin": "余量",
"Mass": "质量",
"Material": "材质",
"Mid Radius": "中间半径",
"Modeled": "已建模",
"More toolbar panels": "更多工具栏面板",
"Motion": "运动",
"Move Type": "移动类型",
"Move earlier": "向前移动",
"Move later": "向后移动",
"Name": "名称",
"No crash reports.": "没有崩溃报告。",
"No matching appearances": "没有匹配的外观",
"Not available yet": "暂不可用",
"Opacity": "不透明度",
"Open GitHub draft": "打开 GitHub 草稿",
"Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.": "使用 Rust 编写的开源参数化 3D CAD。基于 MIT OR Apache-2.0 许可。",
"Other side": "另一侧",
"Parameter values": "参数值",
"Per Edge": "每条边",
"Pick the tool bodies": "选择工具实体",
"Place at the origin instead": "改为放置在原点",
"Playback": "播放",
"Position": "位置",
"Position (0 to 1)": "位置(0 到 1)",
"Position along the edge, 0 at the start end, 1 at the other": "沿边的位置,起点端为 0,另一端为 1",
"Preview leaves the current design unchanged.": "预览不会更改当前设计。",
"RECENT": "最近使用",
"Radius Fit": "半径拟合",
"Ratio": "比率",
"Recover": "恢复",
"Recover unsaved design?": "恢复未保存的设计?",
"Remove Tools": "移除工具",
"Remove from list": "从列表移除",
"Remove private design details or paths before sharing.": "共享前请移除私密设计细节或路径。",
"Remove the column": "移除此列",
"Remove the track": "移除轨迹",
"Remove this point": "移除此点",
"Report a bug — review a screenshot and open a GitHub draft": "报告缺陷 — 复核截图并开启 GitHub 草稿",
"Reset Toolbar": "重置工具栏",
"Restore the inherited component or physical material appearance": "还原继承的组件或物理材质外观",
"Result": "结果",
"Retake screenshot": "重新截图",
"Reverse": "反向",
"Reverse Direction": "反向",
"Revert": "还原",
"Revert All Positions": "还原所有位置",
"Review the report and screenshot before opening GitHub.": "打开 GitHub 前请审阅报告和截图。",
"Rim Edge": "轮缘边",
"Roll History Marker Here": "将历史标记滚动到此处",
"Rule": "规则",
"Saved preview. Geometry is being checked before editing.": "已保存预览。编辑前正在检查几何。",
"Scale factor": "比例因子",
"Search": "搜索",
"Search commands": "搜索命令",
"Select bodies (a face picks its body)": "选择实体(单击面即选择其所属实体)",
"Select bodies or faces instead": "改为选择实体或面",
"Selection filter": "选择筛选",
"Semantic faces share their appearance across instances.": "语义面在各实例间共享外观。",
"Shape": "形状",
"Show Agent Cursor": "显示代理光标",
"Show in folder": "在文件夹中显示",
"Size": "大小",
"Sketch plane has no body-face attachment.": "草图平面没有实体面附着。",
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft 在这些设计保存前已关闭。可以打开它们的上次自动保存。",
"Speed": "速度",
"Stand it up": "将其立起",
"Stand it up: a quarter turn about X, as one Move feature you can undo.": "将其立起:绕 X 轴旋转四分之一周,作为一个可撤销的移动特征。",
"Stand this part up?": "立起此零件?",
"Step": "步骤",
"Steps": "步数",
"Suppressed": "已抑制",
"Swap Ends": "交换端点",
"Tangent Chain": "相切链",
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "虚线草图是已保存的平面引导。选择一个新面以修复附着。",
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "保存的作者实例缺失。重新绑定前请选择有效的作者放置。",
"Then": "操作",
"These features will be deleted:": "以下特征将被删除:",
"These features will fail:": "以下特征将失败:",
"This child is shared. Deleting it removes it from every copy of its parent.": "此子项是共享的。删除它会从其父项的每个副本中移除。",
"This child is shared. Position changes apply to every copy of its parent.": "此子项是共享的。位置更改会应用到其父项的每个副本。",
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "此面无法提供持久附着。请选择其他面或显式使用固定平面。",
"To Offset": "偏移",
"Tolerance": "公差",
"Type": "类型",
"Updating the preview": "正在更新预览",
"Use fixed plane": "使用固定平面",
"VIEW": "视图",
"Value": "值",
"Volume": "体积",
"Wall": "壁厚",
"When": "条件",
"Width": "宽度",
"Will fail": "将失败",
"angle, height": "角度、高度",
"command name": "命令名称",
"expression, e.g. 25 mm": "表达式,例如 25 mm",
"fits the face, e.g. M8": "适配该面,例如 M8",
"from the rule": "来自规则",
"needs two joints": "需要两个接头",
"new configuration": "新配置",
"new name": "新名称",
"new parameter": "新参数",
"no interference": "无干涉",
"no joint that moves yet": "尚无运动接头",
"no picture": "无图片",
"pick a hole to seat it, or OK places it at the origin": "选择一个孔放置,或单击确定放置在原点",
"pick one or two vertices, edges or faces": "选择一个或两个顶点、边或面",
"the parts library is not available": "零件库不可用",
"to the next face": "到下一个面",
"Revert Position": "恢复位置",
"Construction": "构造",
"Faces": "面",
"Edges": "边",
"Vertices": "顶点",
"Sketch geometry and profiles": "草图几何和轮廓",
"Sketch curves": "草图曲线",
"Sketch points": "草图点",
"Sketch dimensions": "草图尺寸",
"Sketch constraints": "草图约束",
"0 turns it off": "0 表示关闭",
"Show the pointer an AI agent moves (#35)": "显示 AI 代理移动的指针(#35)",
"Follow the camera (Look At sketches)": "跟随相机(注视草图)",
"Flat": "平面",
"Save Rule": "保存规则",
"Preview Row": "预览行",
"Add condition": "添加条件",
"Apply to selection": "应用到选择",
"Remove override": "移除覆盖",
"Copy screenshot": "复制截图",
"Remove screenshot": "移除截图",
"Use active author placement": "使用活动作者放置",
"Reattach selected face": "重新附着所选面",
"Use saved fixed plane": "使用已保存的固定平面",
"Use selected face as fixed plane": "使用所选面作为固定平面",
"View navigator": "视图导航器",
"Signed axes": "带符号轴",
"Cube": "立方体",
"Middle double-click": "中键双击",
"Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "在视图中双击中键:主视图(主视图,框住所显示的内容)、适合窗口(框住所显示的内容,保持方向)或无动作",
"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).": "“选择其他”:在模型上按住左键(或右键单击 › 选择其他…)列出光标下的所有对象;在同一位置再次单击可选择该处的下一个对象。双击中键转到主视图(首选项 › 导航)。",
"cmd.render_backends": "渲染后端",
"cmd.view_navigate": "导航视图",
"ui.animateJoint": "动画接头",
"ui.calibrate": "校准",
"ui.customizeToolbar": "自定义工具栏…",
"ui.editJointLimits": "编辑接头限制",
"ui.exportDxf": "导出 DXF…",
"ui.findInTimeline": "在时间线中查找",
"ui.findInWindow": "在窗口中查找",
"ui.fit": "适合",
"ui.groupSelected": "将所选编组",
"ui.hideAxes": "隐藏轴",
"ui.hideDimension": "隐藏尺寸",
"ui.hideOrigin": "隐藏原点",
"ui.hidePlanes": "隐藏平面",
"ui.hideProfile": "隐藏轮廓",
"ui.homeView": "主视图",
"ui.isolate": "隔离",
"ui.lock": "锁定",
"ui.newSketchGroup": "新建草图组",
"ui.normalConstruction": "普通 / 构造",
"ui.orbit": "环绕",
"ui.heading_position": "位置",
"ui.pan": "平移",
"ui.pinToShortcuts": "固定到快捷方式",
"ui.pinToToolbar": "固定到工具栏",
"ui.properties": "属性",
"ui.removeSection": "移除剖面",
"ui.removeFromToolbar": "从工具栏移除",
"ui.repeat": "重复",
"ui.report_a_bug": "报告缺陷",
"ui.restore": "还原",
"ui.heading_setup": "设置",
"ui.selectOther": "选择其他…",
"ui.selectTangentChain": "选择相切链",
"ui.showAll": "显示全部",
"ui.showAllBodies": "显示所有实体",
"ui.showAxes": "显示轴",
"ui.showDimension": "显示尺寸",
"ui.showOrigin": "显示原点",
"ui.showPlanes": "显示平面",
"ui.suppress": "抑制",
"ui.command_palette_hint": "输入命令,例如 {example} — 或按 S 搜索",
"ui.unground": "取消固定",
"ui.unisolate": "取消隔离",
"ui.unlock": "解锁",
"ui.unsuppress": "取消抑制",
"ui.updateToCurrentView": "更新为当前视图",
"ui.zoom": "缩放",
"ui.command_done": "{command} 完成",
"timeline.group": "新建特征文件夹",
"timeline.ungroup": "移除特征文件夹",
"timeline.rename_group": "重命名特征文件夹",
"timeline.move_to_group": "将特征移至文件夹",
"timeline.move_many": "移动时间线项目",
"timeline.reorder_many": "特征重新排序",
"ui.show_construction_geometries": "构造几何图元",
"ui.total_angle": "总角度",
"sketch_text.builtin_font": "{family}(内置)",
"sketch_text.font_search": "搜索字体",
"sketch_text.loading_fonts": "正在查找已安装字体…",
"sketch_text.use_font_file": "使用字体文件 {path}",
"sketch_text.bold": "粗体",
"sketch_text.italic": "斜体",
"sketch_text.align_left": "左对齐",
"sketch_text.align_center": "居中",
"sketch_text.align_right": "右对齐",
"sketch_text.path_hint": "文字所跟随的草图曲线:先单击此处,再选择曲线",
"sketch_text.path_clear": "将文字移离其曲线",
"Style": "样式",
"Alignment": "对齐",
"Letter Spacing": "字间距",
"Line Spacing": "行间距",
"Path": "路径",
"sketch_text.on_path": "文字沿路径排列:由曲线放置",
"ui.analysis": "分析",
"ui.edit_section_analysis": "编辑截面分析",
"cmd.material_properties_validate":"验证物理材料数据"
}
+40
View File
@@ -0,0 +1,40 @@
//! Built-in physical materials: densities in g/cm³ and sRGB colours.
pub const DEFAULT_DENSITY: f64 = 1.29;
/// Physical materials: name and density (g/cm³).
pub const MATERIALS: [(&str, f64); 14] = [
("Default", DEFAULT_DENSITY),
("Steel", 7.85),
("Stainless Steel", 8.0),
("Aluminum", 2.70),
("Brass", 8.50),
("Copper", 8.96),
("Titanium", 4.43),
("Cast Iron", 7.20),
("ABS Plastic", 1.06),
("PLA", 1.24),
("Nylon", 1.14),
("Polycarbonate", 1.20),
("Oak", 0.75),
("Glass", 2.50),
];
/// How a physical material looks (sRGB); the default material has no colour of its own.
pub fn material_color(name: &str) -> Option<[u8; 3]> {
Some(match name {
"Steel" => [158, 164, 172],
"Stainless Steel" => [186, 191, 198],
"Aluminum" => [204, 208, 214],
"Brass" => [208, 172, 88],
"Copper" => [204, 122, 80],
"Titanium" => [150, 148, 158],
"Cast Iron" => [104, 104, 110],
"ABS Plastic" => [222, 222, 216],
"PLA" => [110, 160, 222],
"Nylon" => [236, 230, 214],
"Polycarbonate" => [196, 216, 232],
"Oak" => [190, 146, 92],
"Glass" => [170, 208, 218],
_ => return None,
})
}
@@ -5,10 +5,12 @@ GenericName=3D CAD
Comment=Parametric 3D CAD: sketch, constrain, extrude, and change a parameter to rebuild
Exec=solidcraft %f
TryExec=solidcraft
Icon=ai.storyteller.solvecraft
Icon=ai.storyteller.solidcraft
Terminal=false
StartupNotify=true
StartupWMClass=solidcraft
StartupWMClass=ai.storyteller.solidcraft
Categories=Graphics;3DGraphics;Engineering;
Keywords=cad;3d;parametric;sketch;step;stl;modelling;modeling;
MimeType=application/x-solidcraft;model/step;model/iges;model/stl;model/3mf;
# Releases before 0.8 used this desktop id; launchers keep pinned entries across the rename.
X-Flatpak-RenamedFrom=ai.storyteller.solvecraft.desktop;
@@ -2,7 +2,7 @@
<!-- AppStream metadata (software centres, Flathub). Template: package.sh substitutes @VERSION@
and @DATE@ from Cargo.toml and the build date. -->
<component type="desktop-application">
<id>ai.storyteller.solvecraft</id>
<id>ai.storyteller.solidcraft</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT OR Apache-2.0</project_license>
<name>SolidCraft</name>
@@ -24,8 +24,8 @@
<li>Every action is a command, scriptable from a CLI, a JSON control channel and an MCP server</li>
</ul>
</description>
<launchable type="desktop-id">ai.storyteller.solvecraft.desktop</launchable>
<icon type="stock">ai.storyteller.solvecraft</icon>
<launchable type="desktop-id">ai.storyteller.solidcraft.desktop</launchable>
<icon type="stock">ai.storyteller.solidcraft</icon>
<url type="homepage">https://bherbruck.github.io/solidcraft/</url>
<url type="bugtracker">https://github.com/bherbruck/solidcraft/issues</url>
<url type="help">https://bherbruck.github.io/solidcraft/</url>
@@ -56,7 +56,13 @@
<binary>solidcraft</binary>
<binary>solidcraft-cli</binary>
<mediatype>application/x-solidcraft</mediatype>
<!-- The desktop id of releases before 0.8 (docs/compatibility.md). -->
<id>ai.storyteller.solvecraft.desktop</id>
</provides>
<!-- Releases before 0.8 used the app id ai.storyteller.solvecraft (docs/compatibility.md). -->
<replaces>
<id>ai.storyteller.solvecraft</id>
</replaces>
<content_rating type="oars-1.1"/>
<releases>
<release version="@VERSION@" date="@DATE@"/>
@@ -4,7 +4,7 @@
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-solidcraft">
<comment>SolidCraft design</comment>
<icon name="ai.storyteller.solvecraft"/>
<icon name="ai.storyteller.solidcraft"/>
<sub-class-of type="application/json"/>
<glob pattern="*.solidcraft"/>
<!-- Compatibility shim (docs/compatibility.md): designs saved before the rename. -->
File renamed without changes.
File renamed without changes.
File renamed without changes.
+316
View File
@@ -0,0 +1,316 @@
struct U {
vp: mat4x4<f32>,
back: vec4<f32>, // xyz = toward the eye, w = 1 for linear output
screen: vec4<f32>, // viewport width, height (px), depth bias (see `biased_z`), silhouettes on (1)
clip: vec4<f32>, // section plane: normal, d (zero normal: no section)
cap: vec4<f32>, // colour of the inside of cut bodies
ana: vec4<f32>, // surface analysis: mode (1 zebra, 2 draft, 3 curvature), parameter
pull: vec4<f32>, // draft pull direction
acc_u: vec4<f32>, // accessibility map axes (xyz, offset)
acc_v: vec4<f32>,
eye: vec4<f32>, // eye position, w = 1 for perspective (0: orthographic, looking along -back)
};
@group(1) @binding(2) var acc_t: texture_2d<f32>;
fn clipped(p: vec3<f32>) -> bool {
return length(u.clip.xyz) > 0.5 && dot(u.clip.xyz, p) > u.clip.w;
}
@group(0) @binding(0) var<uniform> u: U;
fn to_linear(c: vec3<f32>) -> vec3<f32> {
return select(c, pow((c + vec3<f32>(0.055)) / vec3<f32>(1.055), vec3<f32>(2.4)), c > vec3<f32>(0.04045));
}
fn out_color(c: vec4<f32>) -> vec4<f32> {
if (u.back.w > 0.5) {
return vec4<f32>(to_linear(c.rgb), c.a);
}
return c;
}
struct TOut {
@builtin(position) pos: vec4<f32>,
@location(0) n: vec3<f32>,
@location(1) c: vec4<f32>,
@location(2) wp: vec3<f32>,
};
@vertex
fn vs_tri(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: vec4<f32>) -> TOut {
var o: TOut;
o.pos = u.vp * vec4<f32>(p, 1.0);
o.n = n;
o.c = c;
o.wp = p;
return o;
}
/// Opaque model surfaces: cut by the section plane, the inside shows as a flat cap colour.
@fragment
fn fs_solid(i: TOut, @builtin(front_facing) front: bool) -> @location(0) vec4<f32> {
let c = solid(i, front);
return c;
}
struct CutOut {
@location(0) c: vec4<f32>,
@builtin(frag_depth) depth: f32,
};
/// The section cap (the inside seen through the cut, drawn on the far walls' back faces where
/// they are the visible surface) again, at the depth of the section plane rather than of the
/// far wall, so the cut face hides what lies inside the body instead of showing it like a
/// pane of glass.
@fragment
fn fs_solid_cut(i: TOut, @builtin(front_facing) front: bool) -> CutOut {
var o: CutOut;
o.c = solid(i, front);
o.depth = cap_depth(i.wp, i.pos.z);
return o;
}
/// Depth of the section plane on the view ray through `wp` (a far wall seen through the cut),
/// never behind the wall itself (`z`).
fn cap_depth(wp: vec3<f32>, z: f32) -> f32 {
var dir = -u.back.xyz;
if (u.eye.w > 0.5) { dir = wp - u.eye.xyz; }
let dn = dot(u.clip.xyz, dir);
if (abs(dn) < 1e-9) { return z; }
let p = wp + dir * ((u.clip.w - dot(u.clip.xyz, wp)) / dn);
let c = u.vp * vec4<f32>(p, 1.0);
if (c.w <= 0.0) { return z; }
return clamp(c.z / c.w, 0.0, z);
}
fn solid(i: TOut, front: bool) -> vec4<f32> {
// Derivatives first, in uniform control flow (browsers' WGSL compilers reject them after
// a branch that returns; the whole shader module then fails and nothing is drawn).
let k = curvature(i);
let ndv = dot(normalize(i.n), normalize(u.back.xyz));
let fw = fwidth(ndv);
if (clipped(i.wp)) { discard; }
if (!front && length(u.clip.xyz) > 0.5) {
// The cut face in its body's colour (as Fusion's "from component"), tinted toward the
// cap colour so it reads as a cut, hatched at 45 degrees on screen.
let base = mix(i.c.rgb, u.cap.rgb, 0.35);
let h = fract((i.pos.x + i.pos.y) / 9.0);
return out_color(vec4<f32>(select(base, base * 0.6, h < 0.22), 1.0));
}
if (u.ana.x > 0.5 && length(i.n) > 0.5) {
return analysis(i, k);
}
let c = shade(i);
// Silhouettes (styles with edges): where a curved surface turns away from the eye, a line
// about 1.5 px wide, on the visible surface only (depth-correct).
if (u.screen.w > 0.5 && length(i.n) > 0.5 && fw > 1e-6) {
let a = 1.0 - smoothstep(fw * 0.8, fw * 2.0, abs(ndv));
return vec4<f32>(mix(c.rgb, out_color(vec4<f32>(0.07, 0.08, 0.10, 1.0)).rgb, a), c.a);
}
return c;
}
/// How fast the normal turns across the pixel (per mm).
fn curvature(i: TOut) -> f32 {
let nn = normalize(i.n);
let kx = length(dpdx(nn)) / max(length(dpdx(i.wp)), 1e-6);
let ky = length(dpdy(nn)) / max(length(dpdy(i.wp)), 1e-6);
return max(kx, ky);
}
/// Surface analysis colours: zebra stripes, draft angle bands, curvature map (`k`: the
/// surface's curvature here, worked out by the caller).
fn analysis(i: TOut, k: f32) -> vec4<f32> {
let v = normalize(u.back.xyz);
var n = normalize(i.n);
if (dot(n, v) < 0.0) { n = -n; }
let lit = 0.55 + 0.45 * max(dot(n, v), 0.0);
if (u.ana.x < 1.5) {
// Reflections of upright light bars standing around the viewer: the stripe follows
// the reflected ray's sideways direction (horizontal on screen).
let r = reflect(-v, n);
var side = cross(v, vec3<f32>(0.0, 0.0, 1.0));
if (length(side) < 1e-3) { side = vec3<f32>(1.0, 0.0, 0.0); }
let s = sin(dot(r, normalize(side)) * u.ana.y * 3.14159265);
let c = select(0.08, 0.95, s > 0.0);
return out_color(vec4<f32>(vec3<f32>(c), 1.0));
}
if (u.ana.x > 4.5) {
// Reachable from the access direction: facing it and nothing above.
let d = normalize(u.pull.xyz);
let size = i32(u.ana.w);
let px = vec2<i32>(floor(vec2<f32>(dot(i.wp, u.acc_u.xyz) + u.acc_u.w, dot(i.wp, u.acc_v.xyz) + u.acc_v.w) * u.ana.z));
var top = -1e30;
if (px.x >= 0 && px.y >= 0 && px.x < size && px.y < size) {
top = textureLoad(acc_t, px, 0).x;
}
let cn = dot(normalize(i.n), d);
let facing = cn > 0.02;
// Slack of a pixel and a half, more on slopes (the map holds the pixel's highest point).
let slack = u.ana.y * (1.0 + sqrt(max(1.0 - cn * cn, 0.0)) / max(cn, 0.05));
let clear = dot(i.wp, d) >= top - slack;
var c = vec3<f32>(0.85, 0.22, 0.20);
if (facing && clear) { c = vec3<f32>(0.20, 0.75, 0.30); }
return out_color(vec4<f32>(c * lit, 1.0));
}
if (u.ana.x > 3.5) {
// A studio around the model: sky above, a dark floor below, a bright horizon band and
// two soft light panels, seen in a mirror.
let r = reflect(-v, n);
var c = mix(vec3<f32>(0.32, 0.34, 0.38), vec3<f32>(0.80, 0.86, 0.95), smoothstep(-0.05, 0.6, r.z));
c = c + vec3<f32>(0.6) * exp(-abs(r.z) * 40.0);
let side = atan2(r.y, r.x);
c = c + vec3<f32>(0.9) * smoothstep(0.92, 0.98, cos(side * 2.0)) * smoothstep(0.1, 0.3, r.z) * smoothstep(0.8, 0.6, r.z);
if (r.z < -0.02) {
// The floor: lighter toward the horizon, with light-panel streaks along it.
c = mix(vec3<f32>(0.55, 0.56, 0.60), vec3<f32>(0.20, 0.20, 0.22), clamp(-r.z * 1.6, 0.0, 1.0));
c = c + vec3<f32>(0.35) * smoothstep(0.95, 0.99, cos(side * 3.0));
}
return out_color(vec4<f32>(min(c, vec3<f32>(1.0)), 1.0));
}
if (u.ana.x < 2.5) {
let a = degrees(asin(clamp(dot(normalize(i.n), normalize(u.pull.xyz)), -1.0, 1.0)));
var c = vec3<f32>(0.95, 0.80, 0.15);
if (a >= u.ana.y) { c = vec3<f32>(0.20, 0.75, 0.30); }
if (a <= -u.ana.y) { c = vec3<f32>(0.85, 0.22, 0.20); }
return out_color(vec4<f32>(c * lit, 1.0));
}
// Curvature from how fast the normal turns across the pixel.
let t = clamp(k * u.ana.y, 0.0, 1.0);
let c = select(mix(vec3<f32>(0.10, 0.80, 0.25), vec3<f32>(0.90, 0.15, 0.10), t * 2.0 - 1.0),
mix(vec3<f32>(0.15, 0.35, 0.95), vec3<f32>(0.10, 0.80, 0.25), t * 2.0), t < 0.5);
return out_color(vec4<f32>(c * lit, 1.0));
}
@fragment
fn fs_tri(i: TOut) -> @location(0) vec4<f32> {
if (clipped(i.wp)) { discard; }
return shade(i);
}
fn shade(i: TOut) -> vec4<f32> {
// A zero normal marks flat colour (selection fills, translucent planes).
if (length(i.n) < 0.5) {
return out_color(i.c);
}
let v = normalize(u.back.xyz);
var n = normalize(i.n);
if (dot(n, v) < 0.0) { n = -n; }
let key = normalize(v * 0.8 + vec3<f32>(-0.3, 0.2, 0.6));
let diff = max(dot(n, key), 0.0);
let fill = max(dot(n, v), 0.0);
let h = normalize(key + v);
let spec = pow(max(dot(n, h), 0.0), 40.0) * 0.25;
let k = 0.42 + 0.38 * diff + 0.25 * fill;
return out_color(vec4<f32>(min(i.c.rgb * k + vec3<f32>(spec), vec3<f32>(1.0)), i.c.a));
}
/// Clip-space z of `p` (clipped to `c`) pulled `s` depth biases toward the eye (pushed away
/// when negative). Orthographic depth is linear in distance, so the bias is a fixed step of it.
/// Perspective depth is not: near the far plane a fixed step spans tens of millimetres (edges
/// showed through walls), so the point moves toward the eye by a fraction of its distance.
fn biased_z(p: vec3<f32>, c: vec4<f32>, s: f32) -> f32 {
if (u.eye.w > 0.5) {
let q = u.vp * vec4<f32>(mix(p, u.eye.xyz, u.screen.z * 8.0 * s), 1.0);
return q.z / max(q.w, 1e-6) * c.w;
}
return c.z - u.screen.z * s * c.w;
}
@vertex
fn vs_ghost(@location(0) p: vec3<f32>, @location(1) n: vec3<f32>, @location(2) c: vec4<f32>) -> TOut {
var o: TOut;
o.pos = u.vp * vec4<f32>(p, 1.0);
o.pos.z = biased_z(p, o.pos, -4.0);
o.n = n;
o.c = c;
o.wp = p;
return o;
}
struct LOut {
@builtin(position) pos: vec4<f32>,
@location(0) c: vec4<f32>,
@location(1) wp: vec3<f32>,
};
@vertex
fn vs_line(@builtin(vertex_index) vi: u32, @location(0) a: vec3<f32>, @location(1) b: vec3<f32>, @location(2) c: vec4<f32>, @location(3) w: f32) -> LOut {
var pa = a;
var pb = b;
var ca = u.vp * vec4<f32>(a, 1.0);
var cb = u.vp * vec4<f32>(b, 1.0);
// An end behind the near plane (clip z < 0) has no screen point: its divide flips the
// line or drops it whole (ground grid lines running past the eye at a low angle). Cut
// the segment at the plane instead; clip space is linear in the world point.
if (ca.z < 0.0 && cb.z > 0.0) {
let k = ca.z / (ca.z - cb.z);
ca = mix(ca, cb, k);
pa = mix(a, b, k);
} else if (cb.z < 0.0 && ca.z > 0.0) {
let k = cb.z / (cb.z - ca.z);
cb = mix(cb, ca, k);
pb = mix(b, a, k);
}
let half = u.screen.xy * 0.5;
let sa = ca.xy / max(ca.w, 1e-6) * half;
let sb = cb.xy / max(cb.w, 1e-6) * half;
var dir = sb - sa;
let len = length(dir);
if (len < 1e-6) { dir = vec2<f32>(1.0, 0.0); } else { dir = dir / len; }
let nrm = vec2<f32>(-dir.y, dir.x);
// Two triangles: corners (t, side).
var ts = array<f32, 6>(0.0, 1.0, 1.0, 0.0, 1.0, 0.0);
var ss = array<f32, 6>(-1.0, -1.0, 1.0, -1.0, 1.0, 1.0);
let t = ts[vi];
let s = ss[vi];
let base = select(ca, cb, t > 0.5);
let off = nrm * s * w * 0.5 + dir * (t * 2.0 - 1.0) * w * 0.5;
var o: LOut;
o.pos = vec4<f32>(base.xy + off / half * base.w, biased_z(select(pa, pb, t > 0.5), base, 1.0), base.w);
o.c = c;
o.wp = select(pa, pb, t > 0.5);
return o;
}
@fragment
fn fs_line(i: LOut) -> @location(0) vec4<f32> {
if (clipped(i.wp)) { discard; }
return out_color(i.c);
}
struct IOut {
@builtin(position) pos: vec4<f32>,
@location(0) uv: vec2<f32>,
@location(1) a: f32,
@location(2) wp: vec3<f32>,
};
@group(1) @binding(0) var img_t: texture_2d<f32>;
@group(1) @binding(1) var img_s: sampler;
@vertex
fn vs_img(@location(0) p: vec3<f32>, @location(1) uv: vec2<f32>, @location(2) a: f32) -> IOut {
var o: IOut;
o.pos = u.vp * vec4<f32>(p, 1.0);
// Pulled toward the eye like lines, so a decal on a face is not lost in it.
o.pos.z = biased_z(p, o.pos, 1.0);
o.uv = uv;
o.a = a;
o.wp = p;
return o;
}
@fragment
fn fs_img(i: IOut) -> @location(0) vec4<f32> {
let c = textureSample(img_t, img_s, i.uv);
if (clipped(i.wp)) { discard; }
return out_color(vec4<f32>(c.rgb, c.a * i.a));
}
/// Lines the section never cuts (the grid, highlights drawn on top).
@fragment
fn fs_line_all(i: LOut) -> @location(0) vec4<f32> {
return out_color(i.c);
}
+138
View File
@@ -0,0 +1,138 @@
//! Original light and dark palette values. Compiled by the UI theme boundary.
use crate::theme::Tokens;
use egui::Color32;
impl Tokens {
pub const fn light() -> Tokens {
Tokens {
app_bar: Color32::from_rgb(52, 56, 64),
toolbar: Color32::from_rgb(246, 247, 249),
panel: Color32::from_rgb(250, 251, 252),
panel_header: Color32::from_rgb(236, 238, 242),
border: Color32::from_rgb(208, 212, 220),
text: Color32::from_rgb(38, 42, 50),
text_dim: Color32::from_rgb(110, 116, 128),
accent: Color32::from_rgb(38, 120, 218),
accent_soft: Color32::from_rgb(214, 230, 250),
sketch_accent: Color32::from_rgb(46, 150, 90),
error: Color32::from_rgb(214, 54, 54),
warning: Color32::from_rgb(222, 150, 30),
icon: Color32::from_rgb(70, 78, 92),
icon_fill: Color32::from_rgb(178, 196, 222),
timeline: Color32::from_rgb(232, 235, 240),
hover: Color32::from_rgb(226, 234, 246),
sel_face: Color32::from_rgb(76, 127, 203),
sel_edge: Color32::from_rgb(0, 127, 255),
sel_edge_rim: Color32::from_rgb(127, 191, 255),
hover_edge: Color32::from_rgb(0, 0, 0),
hover_edge_halo: Color32::from_rgb(203, 203, 203),
hover_face_lift: 40,
sel_profile: Color32::from_rgb(26, 84, 167),
sel_profile_edge: Color32::from_rgb(47, 109, 198),
hover_profile: Color32::from_rgba_unmultiplied_const(75, 129, 253, 48),
hover_profile_edge: Color32::from_rgb(75, 129, 253),
sel_vertex: Color32::from_rgb(70, 110, 170),
origin_plane: Color32::from_rgba_unmultiplied_const(249, 184, 134, 94),
origin_plane_edge: Color32::from_rgba_unmultiplied_const(214, 140, 84, 200),
origin_plane_hover: Color32::from_rgba_unmultiplied_const(120, 130, 230, 150),
origin_point: Color32::from_rgb(214, 215, 218),
axis_x: Color32::from_rgb(255, 0, 0),
axis_y: Color32::from_rgb(0, 255, 0),
axis_z: Color32::from_rgb(2, 2, 248),
construction_plane: Color32::from_rgba_unmultiplied_const(249, 184, 134, 70),
construction_geom: Color32::from_rgb(232, 140, 48),
body: Color32::from_rgb(176, 186, 198),
body_edge: Color32::from_rgb(40, 44, 52),
manipulator: Color32::from_rgb(40, 110, 220),
preview_add: Color32::from_rgb(112, 160, 226),
preview_sketch: Color32::from_rgb(32, 190, 96),
preview_cut: Color32::from_rgba_unmultiplied_const(232, 84, 44, 110),
cube_face: Color32::from_rgb(236, 239, 244),
cube_border: Color32::from_rgb(85, 94, 110),
cube_text: Color32::from_rgb(38, 42, 50),
cube_hover: Color32::from_rgb(190, 220, 252),
viewport_top: Color32::from_rgb(240, 243, 247),
viewport_bottom: Color32::from_rgb(203, 210, 221),
grid: Color32::from_rgb(196, 202, 212),
grid_major: Color32::from_rgb(170, 178, 190),
body_selected: Color32::from_rgb(110, 170, 235),
sketch: Color32::from_rgb(30, 90, 200),
sketch_fixed: Color32::from_rgb(20, 20, 20),
sketch_point: Color32::BLACK,
sketch_locked: Color32::from_rgb(0, 140, 60),
dimension: Color32::from_rgb(36, 40, 48),
dimension_driven: Color32::from_rgb(120, 126, 138),
sketch_point_free: Color32::from_rgb(30, 90, 200),
section_cap: Color32::from_rgb(214, 128, 72),
dialog_bg: Color32::from_rgb(250, 251, 252),
dialog_border: Color32::from_rgb(208, 212, 220),
chip: Color32::from_rgb(38, 120, 218),
heading_bg: Color32::from_rgb(218, 223, 231),
subheading_bg: Color32::from_rgb(234, 237, 242),
profile_fill: Color32::from_rgba_unmultiplied_const(255, 196, 120, 70),
glyph_bg: Color32::WHITE,
glyph_edge: Color32::from_rgb(190, 110, 100),
glyph_text: Color32::from_rgb(150, 40, 35),
overlay: Color32::from_rgba_unmultiplied_const(255, 255, 255, 235),
rubber_band: Color32::from_rgb(30, 120, 230),
app_bar_text: Color32::from_rgb(230, 234, 242),
timeline_item: Color32::WHITE,
timeline_item_error: Color32::from_rgb(250, 220, 220),
timeline_marker: Color32::from_rgb(70, 76, 88),
field: Color32::WHITE,
box_window: Color32::from_rgb(38, 120, 218),
box_crossing: Color32::from_rgb(40, 160, 80),
}
}
pub const fn dark() -> Tokens {
let mut t = Tokens::light();
t.app_bar = Color32::from_rgb(28, 31, 38);
t.toolbar = Color32::from_rgb(37, 41, 49);
t.panel = Color32::from_rgb(39, 43, 52);
t.panel_header = Color32::from_rgb(47, 52, 62);
t.border = Color32::from_rgb(63, 69, 82);
t.text = Color32::from_rgb(222, 226, 232);
t.text_dim = Color32::from_rgb(148, 155, 168);
t.accent = Color32::from_rgb(58, 136, 238);
t.accent_soft = Color32::from_rgb(42, 70, 110);
t.sketch_accent = Color32::from_rgb(110, 200, 140);
t.error = Color32::from_rgb(240, 100, 96);
t.warning = Color32::from_rgb(236, 170, 70);
t.icon = Color32::from_rgb(208, 214, 226);
t.icon_fill = Color32::from_rgb(86, 112, 150);
t.timeline = Color32::from_rgb(33, 37, 45);
t.hover = Color32::from_rgb(54, 61, 74);
t.hover_edge_halo = Color32::from_rgb(203, 203, 203);
t.origin_point = Color32::from_rgb(214, 215, 218);
t.viewport_top = Color32::from_rgb(46, 52, 64);
t.viewport_bottom = Color32::from_rgb(46, 52, 64);
t.grid = Color32::from_rgb(59, 65, 76);
t.grid_major = Color32::from_rgb(73, 79, 90);
t.body = Color32::from_rgb(152, 156, 162);
t.body_edge = Color32::from_rgb(16, 16, 18);
t.sketch = Color32::from_rgb(92, 150, 255);
t.sketch_fixed = Color32::from_rgb(232, 234, 238);
t.sketch_point = Color32::from_rgb(232, 234, 238);
t.sketch_locked = Color32::from_rgb(96, 204, 116);
t.dimension = Color32::from_rgb(224, 228, 234);
t.dimension_driven = Color32::from_rgb(142, 150, 164);
t.sketch_point_free = Color32::from_rgb(92, 150, 255);
t.profile_fill = Color32::from_rgba_unmultiplied_const(120, 150, 200, 40);
t.dialog_bg = Color32::from_rgb(59, 68, 83);
t.dialog_border = Color32::from_rgb(72, 83, 99);
t.chip = Color32::from_rgb(59, 101, 168);
t.heading_bg = Color32::from_rgb(38, 44, 55);
t.subheading_bg = Color32::from_rgb(48, 55, 68);
t.glyph_bg = Color32::from_rgb(44, 50, 60);
t.glyph_edge = Color32::from_rgb(200, 130, 120);
t.glyph_text = Color32::from_rgb(240, 165, 155);
t.overlay = Color32::from_rgba_unmultiplied_const(34, 38, 46, 240);
t.rubber_band = Color32::from_rgb(92, 160, 255);
t.timeline_item = Color32::from_rgb(52, 58, 70);
t.timeline_item_error = Color32::from_rgb(110, 50, 54);
t.timeline_marker = Color32::from_rgb(170, 178, 192);
t.field = Color32::from_rgb(30, 34, 41);
t.box_window = Color32::from_rgb(70, 150, 255);
t.box_crossing = Color32::from_rgb(80, 200, 120);
t
}
}
+4
View File
@@ -0,0 +1,4 @@
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #e8ebf0; }
#solidcraft_canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
#solidcraft_loading { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center;
color: #4a5060; font: 14px system-ui, sans-serif; pointer-events: none; }
+6 -25
View File
@@ -103,28 +103,9 @@ impl Appearances {
}
/// The built-in appearance library: (name, colour, opacity).
pub const LIBRARY: [(&str, [u8; 3], f64); 20] = [
("Aluminum - Satin", [204, 208, 214], 1.0),
("Aluminum - Anodized Black", [44, 46, 50], 1.0),
("Aluminum - Anodized Blue", [52, 92, 160], 1.0),
("Steel - Satin", [158, 164, 172], 1.0),
("Stainless Steel - Brushed", [186, 191, 198], 1.0),
("Cast Iron", [104, 104, 110], 1.0),
("Brass - Polished", [208, 172, 88], 1.0),
("Copper", [204, 122, 80], 1.0),
("Titanium", [150, 148, 158], 1.0),
("ABS - White", [236, 236, 230], 1.0),
("ABS - Black", [36, 36, 38], 1.0),
("ABS - Red", [196, 40, 40], 1.0),
("ABS - Blue", [40, 90, 186], 1.0),
("ABS - Yellow", [236, 196, 40], 1.0),
("ABS - Green", [52, 150, 80], 1.0),
("Rubber - Black", [28, 28, 28], 1.0),
("Glass - Clear", [190, 220, 228], 0.25),
("Glass - Smoked", [90, 96, 104], 0.45),
("Wood - Oak", [190, 146, 92], 1.0),
("Paint - Gloss White", [246, 246, 244], 1.0),
];
#[path = "../../../assets/appearances/library.rs"]
mod defaults;
pub use defaults::LIBRARY;
/// A library appearance by name (case-insensitive).
pub fn library(name: &str) -> Option<Look> {
@@ -147,7 +128,7 @@ impl Document {
.get(name)
.cloned()
.or_else(|| self.appearances.components.get(&self.model_body_component(body)).cloned())
.or_else(|| self.materials.get(name).and_then(|material| material_look(material)))
.or_else(|| self.materials.get(name).and_then(|material| self.material_look(material)))
}
/// Effective body opacity, including paint carried from an imported body.
pub fn model_body_opacity(&self, body: &crate::ModelBody) -> f64 {
@@ -168,7 +149,7 @@ impl Document {
let c = self.body_component(name, feature);
(c != 0).then(|| self.appearances.components.get(&c).cloned()).flatten()
})
.or_else(|| self.materials.get(name).and_then(|m| material_look(m)))
.or_else(|| self.materials.get(name).and_then(|m| self.material_look(m)))
}
/// Face appearances on a body: (point on the face, look).
@@ -263,7 +244,7 @@ pub fn pick_tolerance(b: &crate::ModelBody) -> f64 {
}
/// The point of triangle abc nearest p.
fn closest_on_triangle(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
pub(crate) fn closest_on_triangle(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
let (ab, ac, ap) = (b - a, c - a, p - a);
let (d1, d2) = (ab.dot(ap), ac.dot(ap));
if d1 <= 0.0 && d2 <= 0.0 {
+28 -8
View File
@@ -124,6 +124,10 @@ fn identity() -> Mat {
IDENTITY
}
/// How deep and how many placements [`Document::placements`] walks.
pub const MAX_PLACEMENT_DEPTH: usize = 64;
pub const MAX_PLACEMENTS: usize = 100_000;
fn map_plane(m: &Mat, p: &Plane) -> Plane {
let (o, x, y) = (apply_point(m, p.origin), apply_vector(m, p.x), apply_vector(m, p.y));
Plane::new(o, x, y).unwrap_or(*p)
@@ -221,20 +225,22 @@ impl FeatureKind {
pt(&mut t.point);
}
}
FeatureKind::Revolve { axis: AxisRef::Line { origin, dir: d }, .. } => {
FeatureKind::Revolve { axis: AxisRef::Line { origin, dir: d }, .. }
| FeatureKind::SurfaceRevolve { axis: AxisRef::Line { origin, dir: d }, .. } => {
pt(origin);
dir(d);
}
FeatureKind::Fillet { edges, style, .. } => {
edges.iter_mut().for_each(pt);
if let crate::FilletStyle::Variable { start, .. } = style {
if let crate::FilletStyle::Variable { start, .. } | crate::FilletStyle::Spline { start, .. } = style {
pt(start);
}
}
FeatureKind::Chamfer { edges, .. } => edges.iter_mut().for_each(pt),
FeatureKind::Shell { faces, .. } | FeatureKind::OffsetFace { faces, .. } | FeatureKind::DeleteFace { faces, .. } => {
faces.iter_mut().for_each(pt)
}
FeatureKind::Shell { faces, .. }
| FeatureKind::OffsetFace { faces, .. }
| FeatureKind::DeleteFace { faces, .. }
| FeatureKind::SurfaceOffset { faces, .. } => faces.iter_mut().for_each(pt),
FeatureKind::Draft { faces, neutral, pull, .. } => {
faces.iter_mut().for_each(pt);
map_plane_ref(m, neutral);
@@ -263,8 +269,12 @@ impl FeatureKind {
dir(direction);
}
FeatureKind::Thread { face, .. } => pt(face),
FeatureKind::SheetFlange { edges, .. } | FeatureKind::SheetHem { edges, .. } => edges.iter_mut().for_each(pt),
FeatureKind::SheetConvert { face, .. } => pt(face),
FeatureKind::SheetFlange { edges, .. } => edges.iter_mut().for_each(|e| pt(&mut e.point)),
FeatureKind::SheetHem { edges, .. } => edges.iter_mut().for_each(pt),
FeatureKind::SheetConvert { face, bends, .. } => {
pt(face);
bends.iter_mut().for_each(pt);
}
FeatureKind::SheetFold { a, b, fixed, .. } => {
pt(a);
pt(b);
@@ -317,6 +327,10 @@ impl FeatureKind {
}
FeatureKind::Revolve { .. }
| FeatureKind::Extrude { .. }
| FeatureKind::SurfaceExtrude { .. }
| FeatureKind::SurfaceRevolve { .. }
| FeatureKind::SurfaceSweep { .. }
| FeatureKind::SurfaceLoft { .. }
| FeatureKind::Combine { .. }
| FeatureKind::Loft { .. }
| FeatureKind::Sweep { .. }
@@ -332,6 +346,7 @@ impl FeatureKind {
| FeatureKind::MeshConvert { .. }
| FeatureKind::BodyTransfer { .. } => {}
FeatureKind::Patch { edges, .. } | FeatureKind::SurfaceExtend { edges, .. } => edges.iter_mut().for_each(pt),
FeatureKind::Unstitch { faces, .. } | FeatureKind::ReverseNormal { faces, .. } => faces.iter_mut().for_each(pt),
FeatureKind::SurfaceTrim { plane, tool, keep, .. } => {
map_plane_ref(m, plane);
if let Some(t) = tool {
@@ -365,13 +380,18 @@ impl Document {
}
/// Every placement of each component: (component, occurrence path ids, world transform).
/// A branch stops past [`MAX_PLACEMENT_DEPTH`] levels, the walk past [`MAX_PLACEMENTS`] placements.
pub fn placements(&self) -> Vec<(u64, Vec<u64>, Mat)> {
let mut out = vec![(0, Vec::new(), IDENTITY)];
let mut stack: Vec<(u64, Vec<u64>, Mat)> = vec![(0, Vec::new(), IDENTITY)];
while let Some((comp, path, m)) = stack.pop() {
if path.len() > 64 || out.len() > 100_000 {
if out.len() > MAX_PLACEMENTS {
break;
}
// Only this branch stops past the depth limit; the others are still walked.
if path.len() > MAX_PLACEMENT_DEPTH {
continue;
}
for o in self.occurrences.iter().filter(|o| o.parent == comp) {
let mut p = path.clone();
p.push(o.id);
Loaded 100 of 614 files, more files were not shown because too many files have changed in this diff. Show more