e81a201ff8ef91c6a6fa52907ecf68fa2ffda3f5
1049
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e81a201ff8 |
Drawing W06a: parallel hidden lines and shared work limits (#2455) (#3013)
* geom/hlr: per-curve work on worker threads, shared work limits Silhouette tracing, screen sampling, shadow cuts, classification, coverage and the crossing search (by grid rows) with crossing refinement run on worker threads through one ordered map: results go back in item order, so drawings are the same bit for bit on any number of threads. Options::threads picks the workers (0 = one per core, small views stay on the calling thread); the browser build stays serial. Placements sharing a body and a turn share one silhouette tracing. Cancellation is polled before every item and inside long ones from every worker; the first error stops the others; a panicking worker becomes HlrError::Internal instead of escaping. Ray, curve, crossing, shadow and coverage budgets are totals over all threads (shared counters; crossing pairs are counted before the search). Limits: placements and the default max_tracks are hlr::MAX_ITEMS, which is section::MAX_ITEMS (also the brep record limit); trim_samples must be 1..=4096. * docs/drawing-hlr: link the parallel-work PR * geom/hlr: clearer limits and parallel-work docs, faster thread tests docs/drawing-hlr.md now separates input limits (with the error each returns: body records fail with Invalid, not Budget) from the work budgets, names every budget's error, and states how errors and panics behave on worker threads. Code comments say the same in plainer terms. The budget and cancellation tests drop the oblique torus, and the freeform pass of the identity test compares one parallel run, which keeps the HLR suite under 4 s. * geom/hlr: poll cancellation inside long items, find spline spans by bisection Review found two items a worker could run for seconds without seeing a cancellation: tracing a body with many curved surfaces, and sampling one long spline (up to 4096 control points). Silhouette tracing now asks before each surface, screen sampling at every refinement pass, the cuts at left-out faces every 4096 chords and coverage every 65536 checks. The long spline was slow because every point and every part hull scanned all of its knot spans. de Boor evaluation now finds its span by bisection over the ordered knots, and a part's hull starts at its first piece and stops after its last: the same spans, so the same bits (checked on the fixture scenes), and a 4096-point spline edge prepares in 0.9 s instead of 2.2 s and samples in 0.23 s instead of 1.47 s. * geom/hlr: poll cancellation while forming and trimming silhouette sets Review found that forming a body's silhouette sets scanned each face's whole surface group before the first poll, quadratic in the faces on one surface (about 4e8 steps for 20 000 faces). The group scan now stops at its first supported face (groups list faces in order, so that is the same face), the loop polls every 256 faces, and trimming a traced curve asks before each face. A cancellation seen while trimming drops the partial curves, even if the callback answers false later. Output unchanged on the fixture scenes. * geom/hlr: poll cancellation per boxes met when cutting at left-out faces Review found that one chord crossing the screen boxes of very many left-out faces bisects every side without a poll (polls came only every 4096 chords). It now also polls every 1024 boxes met. The docs state the cancellation contract precisely: polls are where listed, and the work between two polls is bounded by the input limits rather than by a time. * docs/drawing-hlr: say where coverage polls cancellation * geom/hlr: body records have brep's own, larger limit now Main raised brep::MAX_ITEMS to 1 000 000 (#3093, which stores whole evaluated models), so it no longer equals section::MAX_ITEMS. hlr::MAX_ITEMS stays section's 100 000 for placements and the default curve budget; the docs and the limits test now say that a body's records are bounded separately, by brep's limit. * geom/hlr: a panicking worker stops the others; the ray budget is HLR's own A worker that panics now sets the shared stop flag while it unwinds, so the other workers end the phase early instead of finishing work the call throws away. The docs no longer compare the ray budget with the occlusion work budget of file.export_view_dxf: the two count different work. * geom/hlr: an ignored timing test, prepare and drawing timed apart hlr_timing prepares four bodies, then draws 100 differently turned placements on one thread, two workers and one worker per core, printing the total, the phase timings and the ray count. SOLIDCRAFT_BENCH_HLR sets the placement count. * geom/hlr: timing scenes for sharing and crossings; placements keep their lines when permuted hlr_timing now starts its prepare clock after the fixtures are built, adds the same grid at one turn (shared definitions against one per placement) and sixteen thin bars crossing 8x8 from above, rotates the thread order over three rounds and checks the drawings match across thread counts outside the timed part. A new test permutes three placements of one prepared box and removes one: each line keeps its own placement index, and the boxes' lines stay exact. * docs/drawing-hlr: the review's limits and fixtures in the parallel tables * geom/hlr: follow the B-rep record into solidcore-record The record, its version and its item limit now come from solidcore-record (#11496): the new tests build their bodies at `solidcore_record::brep::VERSION` and make a spline's curve with `C3::bounded`, as the other fixtures do, and the limits table gives the record's own limit, 4 000 000 items, checked by `brep::check::validate`. --------- Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> |
||
|
|
96bb41c480 |
Drawings: notes on a drawing, the record and commands (#4896) (#11898)
* Issue a revision: drawing.revise (#3874 R1) drawing.revise appends a copy of a row to the issued history: an existing row as it reads, or a new revision with the texts given, in one undo step. A row is issued once. The copy keeps what it read through an edit, a hide or a delete, and nothing is filled in: no date, checker or approval. Issuing used to make a drawing unreadable, as a copy has its row's id and the drawing's check refused any id seen twice. Each id now counts once, and issued ids are unique among themselves. model_version becomes {path, change}, on issued copies only. One function on the session answers it, and answers nothing until version control exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing revisions: numbered 1, 2, 3 (#3874) A drawing's new revisions are numbered 1, 2, 3, never lettered (@CamWight, 3 Nov). drawing.set's revision_numbering is {start: 0 or 1}, the first number. A revision typed by hand is kept as typed; the next one follows the highest number, and drawing.inspect says where numbering starts. A record in letters is refused (drawings haven't shipped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Revision tags on a sheet: add, edit and delete (#3874 R3) A tag gains the sheet it sits on and its place there. drawing.revision.tag.add, .edit and .delete keep them, inspect and every revision reply list them with what each shows, and a sheet's delete counts and takes the tags on it. A deleted row still leaves its tags showing the revision they had. Tags draw on paper once scene text lands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Saved tables on a sheet: drawing.table.add, .edit and .delete (#3874) A drawing saves its parts lists and revision tables, each on a sheet with its definition and place. Rows are never saved. A parts list starts at the top right listing what its sheet documents, else the design; a revision table at the bottom left; headings on top, QTY, rows of 7 mm. Placing a list saves the numbers its view shows, once per view, so what is printed doesn't move. Numbers are the design's: undoing the placing puts back only the drawing, as every drawing step does, and the numbers stay. That one command changes both, and the proof that nothing needs evaluating now allows it. The tables are a ById list, as the balloons and images are, and keep its rules: at most 1000, listed by id. Revision tags take their own id type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A balloon reads its row in a parts list (#3874 L1) A balloon can name the parts list it reads; else it reads its sheet's only list, else the drawing's only one, else none, with the choices listed. Its row's item is worked out on each read and never saved: drawing.balloon.inspect gives item and row_status (ok, no_list, reference, no_row or not_found). A lost row never removes the balloon. A list's delete reports the balloons that named it and clears their links. Bom::item_of cuts a path from the top of the design to start below the assembly listed, and row_of is no longer test-only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Notes on a drawing: the record and commands (W19e, #4896) A note sits on a sheet, a view, or an edge or corner of a view's model, saved by name as a dimension's is, with the title block's text and fields, its place and its text height. drawing.note.add, .edit and .inspect, one undo step each, no model evaluated. Its one leader is worked out, never saved: a lost reference keeps the note and its text, with no leader. Anchor is one type so other symbols placed on a sheet can use it. A view's notes go with its copy and delete; a sheet's with the sheet. The plan left delete for later, but selection.delete now handles a drawing's objects, so notes join it. Balloons and notes share Lookup::middle. Drawing them waits for text in the paper scene (slice 12). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b841eb939f |
Merge main into drawings (Nov 4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
954232cef1 |
AI client setup: preserve configuration files when a write fails (#11363) (#11410)
* claim: #11363 * AI client setup: replace configuration files atomically (#11363) |
||
|
|
41a450be7c |
i18n: add Georgian (ka) interface language (#11501)
A Georgian catalog (assets/locales/ka.json) carrying every English message, registered in crates/ui-egui/src/i18n.rs, with a row in the new-language test, a pending attribution file and a terminology note in docs/localization.md. The catalog is Mkhedruli only; egui's built-in Hack already draws it. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b227924be9 |
Render: the Cancel button keeps its full label during a render (#11222) (#11241)
* claim: #11222 * Render: the progress row keeps Cancel whole (#11222) The progress card is an egui area, laid out within last frame's size. When the status grew (9 % to 10 %, a longer phase or name), the caption and the Cancel button were truncated, down to "..." with a long shot name. progress_action now takes the name and the status apart and gives each part an explicit width from the room the caller offers (the canvas for the floating card): Cancel and the status always show whole, a long name is cut with an ellipsis (full name on hover), then the meter narrows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
28b47059bb |
Agent chat: a text selection dragged past the edge scrolls the conversation (#11527) (#11533)
* claim: #11527 * Agent chat: a text selection dragged past the edge scrolls the conversation (#11527) Holding a text selection near or past the top or bottom of the conversation scrolls it, faster the further out, so a long answer can be selected in one drag. egui scrolls a label's selection only for the label under the cursor, and not at all for a table, whose label sits in its own horizontal scroll area. egui also leaves a label out of the text selection while it is scrolled out of view: a selection whose start had scrolled away was dropped, and Copy missed the hidden text. The conversation's labels now take part out of view too, clipped to nothing, so they keep their end of the selection and give their text to Copy without being drawn or moving the selection. * Agent chat: only a drag towards the edge scrolls the conversation (#11527) A press on a label creates an empty selection on the press frame, so a click, a double click or a held press on the line by the edge scrolled the conversation, and so did the start of a drag from there inwards. Scrolling now waits until the pointer has moved more than 8 points towards that edge since the press. * Agent chat: approval titles and pending messages keep a selection scrolled away They were plain selectable labels, so a selection that started in one was dropped once its label scrolled out of view, as in the rest of #11527. They now use the same off-screen-aware label as the conversation's text. --------- Co-authored-by: MateuszSychXpl <151755914+MateuszSychXpl@users.noreply.github.com> |
||
|
|
f9dd77e383 |
Configurations: reject invalid suppression values when saving rows (#9088) (#11571)
* claim: #9088 * Configurations: reject invalid suppression values before saving rows (#9088) |
||
|
|
7bd66b7c06 |
Sketch dimensions: enter the value in a small window with step arrows (#7246) (#7251)
* claim: #7246 * feat(sketch): enter a dimension's value in a small window with step arrows (#7246) * Dimension window: fit the layer split, and keep the wheel off the camera over the window Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: bherbruck <brennen.herbruck@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6312c2760a |
MCP list_commands: ask for commands by id prefix or panel, ids and labels only, or a page at a time (#11726) (#11788)
* claim: #11726 * MCP list_commands: ask for commands by id prefix or panel, ids and labels only, or a page at a time (#11726) list_commands answered with the whole catalog, every key of 575 commands (about 350 KB), and could only narrow it by a substring. It now also takes `prefix` (start of the id), `panel` (a whole panel or tab name), `compact` (id and label), `fields` (the keys to keep) and `limit`/`offset` (pages, as {commands, total, offset, next_offset}). With none of them the reply is the same as before. A batch step naming list_commands narrows the same way, and a bad argument is an error that names it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
481687fff3 |
Modeled threads: the thread runs down to a flat face with no uncut block, so a nut can bottom out (#11782)
* Modeled threads: run down to a flat shoulder so a nut can bottom out An external thread running the whole face stopped with its groove clear of a shoulder, leaving up to a turn uncut above it. The groove now runs on past the plane of the face's end and is trimmed there, so its root reaches the shoulder at every angle and nothing past the plane is cut. A shoulder rising back along the face keeps the square stop, as do internal threads, tapers, runouts and lengths stopping short. Where the kernel refuses the trimmed cut, the groove also keeps the square stop and the feature warns. Closes #11610 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Kernel ladder: record the thread cases that now reach their shoulder Six cases gain the faces of the trimmed last turn; every other case is unchanged apart from its timing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Thread at a shoulder: review fixes, and a test of the fallback The shoulder check starts just off the face, as the free-end check does, so a slope rising from the face counts. A groove the trim leaves nothing of now falls back to the square stop too. A test forces the fallback and reads its warning. The ladder's order and report go back to main's, changed only for the cases this moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Thread at a shoulder: check all the radii the groove cuts The check for a shoulder rising along the face looked a fixed distance out, which falls short of the groove on an undersized shaft. It now covers the groove's reach. A test reads how each kind of shoulder is classified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Thread at a shoulder: test the trim that leaves nothing of the groove The test now reaches the path where the half-space keeps nothing, which is the one that must fall back to the square stop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Thread at a shoulder: say so when the shoulder can't be checked If the kernel can't make the rising-shoulder check, the groove keeps the square stop as before, and the feature now warns that its last turn is left uncut. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f74c6605c4 |
Fillet: Mitre or Swept corners where two rounds meet (#11498)
* SMLib wrapper: a swept or mitred turn for constant rounds (Turn) Brep::blend_dropping takes a Turn: Mitre (the default, as before) or Swept, which sets solidcore's FilletSession::set_swept_turns before the corners are made. A swept turn solidcore refuses at CreateFilletCorners comes back as Error::TurnRefused with its reason and place (NoRoom at a concave side edge, else CornerUnsupported). Swept is for Blend::Round only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * Kernel: fillet rounds turn in a mitre or swept (FilletTurn) fillet_removing, fillet_max, fillet_max_each and fillet_each take a FilletTurn and hand it to SMLib, through the reblend of earlier rounds at a later corner too. A swept corner solidcore does not build is refused as invalid input with its reason and place: no smaller radius builds it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * Design: a fillet saves its turn, mitre (default) or swept FeatureKind::Fillet gets `turn` (FilletTurn: mitre, the default and not saved, or swept), one pending format file. Evaluation hands it to the kernel, pattern copies included, and refuses a swept turn with a style, fit, corner or setbacks the kernel's swept corner does not take (SWEPT_TAKES). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * solid.fillet: the turn parameter, and the command's input and output schemas `turn: mitre|swept` on solid.fillet, refused when unknown or with options a swept corner does not take. solid.fillet now has its contract (FilletParams) and reply type (FilletResult), registered with checked!, and leaves schema/unconverted.txt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * Fillet dialog: a Turns choice, Mitre or Swept A Constant radius fillet's Turns row sends `turn: swept`; choosing a narrowed radius or a curvature corner puts it back to Mitre, and Swept puts those back. Its tooltip says what each does where two rounds turn round a corner edge. New strings in a pending locale file (en, de, ja, ko, zh); glossary entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * Fillet turn: the schema tests' fixes and the complete catalogs' strings The no-contract example in the schema tests is solid.chamfer now that solid.fillet has one; the reply test checks the mid-radii form against the contract (solidcore refuses that law's corner yet, #430) instead of building it; Arabic, Hebrew and Vietnamese, complete catalogs, get the new strings. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * Fillet schema test: variable and spline forms checked against the contract solidcore fails a variable round on a box edge at this pin (status 1001, as variable_radius_on_a_box_edge), so the reply test checks those forms against the contract instead of building them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ * Fillet turn: the swept rig cases pass; the MCP no-contract example is solid.chamfer cov-fillet-turn-swept-boss-base-r1 and cov-fillet-turn-swept-pocket-sharp-corner-refused-r1 pass now that solid.fillet takes `turn`: off the rig's known-failing list. The MCP bridge test's example of a command without a contract was solid.fillet, which has one now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5M41G8Nua7HvuFPZkmJ --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
c5bc849913 | Blur: keep themed backing and notice actions accessible (#9810) | ||
|
|
381ff99f70 |
Desktop: one design's long kernel call no longer stops other designs (#11684)
solidcraft-smlib held one process-wide kernel lock for the whole of every solidcore call, so a long call in one design (a modeled thread, a fillet) held up every kernel call in every other design, even after its tab was closed, and a call that never returned held them until restart. Each call now takes a kernel from a pool and the lock is only held to take or put one back. Calls one after another get the same kernel as before; one that starts while another runs gets its own. Each kernel has its own range of procedural blend serials, since a body keeps the serials of the kernel that made it. History commands and delete checks now belong to their design: they wait with its tab, go when it closes, and can't land in another design. An agent's command for a design that left the window is refused at once instead of holding up the requests behind it. Closes #11681 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
ec31745f0a |
Preferences redesign 10/11: Navigation and 3D mouse (#9882) (#10739)
* Preferences: preserve Navigation and 3D mouse alongside other panels * Tests: check preference search visibility after scrolling settles * Tests: identify preference search results through indexed row handles |
||
|
|
5e2a1460ff |
Preferences redesign 9/11: Sketch (#9881) (#10720)
* Preferences: preserve Sketch redesign alongside Graphics * Preferences: share hinted and unhinted blocks across independent panels * Tests: wait for preference search scrolling before checking visible targets * Tests: locate preference search results by their indexed rows |
||
|
|
92a9a5e0f0 |
Preferences redesign 6/11: Layout (#9878) (#10638)
* Preferences: preserve Layout controls alongside the other redesigned panels * Tests: locate the redesigned Layout checkbox by its visible label |
||
|
|
1094503e97 | Preferences: share picture choices without conflicting with panel redesigns (#10937) | ||
|
|
f3977d5c2d |
Preferences redesign 8/11: Design and Tools (#9880) (#10710)
* Redesign Design and Tools preferences * Refine Tools: icon rows for Create, dashed Assembly Move ring, gradient cursor trail * Preserve current behavior when rebasing Design and Tools preferences * Preferences: keep Design search aliases compatible with queued panels * Preferences: let Design and Graphics helpers merge independently |
||
|
|
908fe48c0b |
Preferences: group Shortcuts and use cards for Assistant and Plugins (#9883) (#10765)
* Preferences: group Shortcuts and refine Assistant and Plugins * Preferences: keep Assistant search additions compatible with other panels |
||
|
|
138e3478e6 | Regenerate generated files and fold pending files [skip ci] | ||
|
|
fbd30b0bd0 |
Panels: inspect and change layouts through commands (#11304) (#11454)
* claim: #11218 * Panels: add shared layout rules and retained content runtime * claim: #11304 * Panels: inspect and change layouts through shared commands (#11304) * Panels: account for layout commands in geometry coverage |
||
|
|
372136fa6a |
Preferences redesign 7/11: Graphics (#9879) (#10662)
* Preferences: keep Graphics redesign independent on current main * Preferences: repair Graphics search targets and CI regressions |
||
|
|
dc38349cbf |
Layer split stage 1: the B-rep record, native body file and Vec/Plane come from solidcore-record (#11496)
* Kernel: the B-rep record and the native body file come from solidcore-record SolidCraft's portable B-rep record (`solidcraft_geom::brep`: brep.rs, topology, history, envelope, part, curve2) and the native body file (`solidcraft_kernel::native`: the tagged binary codec and the container) move to solidcore's `solidcore-record` crate: geometry as data, with structural validation and no maths (solidcore's docs/layers.md, stage 1). Every caller uses `solidcore_record::brep` and `solidcore_record::native`; the old copies are deleted. What computes from coordinates stays with the kernel, in geom's `brep`: - `brep::check`: the record's geometric checks (orthonormal frames, non-degenerate planes, unit revolution axes, non-singular placements). `check::validate` and `check::validate_built` are the record's validation and these together, and every caller of `Brep::validate` uses them, so what is accepted is unchanged; - `brep::label`: a surface's natural ranges (a cone's apex), `curve_interval`, `edge_curve_interval` and `form_turns`; - `brep::pcurve::eval`: a pcurve's point; - `geometry::check`, `placement::moved` and `split_edges` become functions on the record. The kernel's native module keeps the conversion between a body and its saved record (metadata.rs) and checks the decoded record's geometry. `native/analytic.rs`, which no module declared, is deleted. solidcore-record is pinned to its rev on solidcore's `layer-record` branch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT * Geometry: Vec2, Vec3, Plane and the profile types come from solidcore-record The plain data of vectors and points, sketch planes and planar profiles (`Seg2`, `Loop2`, `Region2`) moves to `solidcore-record`, with its constants and component-wise operators only (owner decision, solidcore layer split). Their geometry stays in geom as traits: `Vec2Geom`, `Vec3Geom`, `PlaneGeom`, `Seg2Geom`, `Loop2Geom`, `Region2Geom`, `Seg2ArcFit`, `Loop2ArcFit` and `Region2ArcFit`, in scope with `use solidcraft_geom::prelude::*`. Every caller names the types from `solidcore_record`; geom no longer defines or re-exports them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT * Kernel: pin solidcore-record to solidcore main f5e8e9a29 solidcore-record is merged on solidcore main (tangency-labs/solidcore#2519); its crate there is identical to the rev pinned before but for its clippy configuration. The lock is regenerated against the merged rev. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NvoxKMbMFSQU8tzRtoJhtT --------- Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
e438a9bbea |
Web app: choose the release, the release candidate or the edge build (#11419)
The web app is now offered at /app/ (latest release), /app-rc/ (newest release candidate) and /app-edge/ (current main), each with its own browser storage. /app/ is built from the latest release's web zip instead of main. |
||
|
|
9308095744 |
Paper text in the browser: Japanese through the font the web app fetches (#4890 F6) (#10516)
The browser has no font folders, so `font_dirs` is empty there. The first kana or Han a text's font lacks asks for the font the web app already fetches on demand (BIZ UDPGothic); no new file. The text is pending until it comes, then sets whatever it has a glyph for. If the fetch fails, or the font lacks a character, that character is refused by name, and the refusal says the web covers Japanese for now. The web app starts the fetch through its existing loader, once, when a text waits for the font, and hands the font over when it arrives. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
2f483a00c0 |
Drawing text F4: a character missing from the chosen font is drawn with another installed font (#4890) (#10515)
* Drawings: the font a drawing's text is set in, by family (#4890 F1) A drawing names the font family its text is set in (`font`, saved only when set; a pending drawing-format file). `drawing.create` and `drawing.set` take it, and `drawing.inspect` reports it, with `font_note` when this computer sets the text in the built-in font instead ("Futura isn't on this computer: set in Liberation Sans"). Drawings name fonts by family, so they travel: a font file is refused. Paper text finds the family among the installed fonts as sketch text does (`paper_text_in` for given folders), admits each face once a process, and sets a family that is missing or can't be set on paper in the built-in font. Bold takes the family's bold face; the serif flag comes from the face's OS/2 family class. The fixed list of drawing fonts is gone. New drawings made in the app take the interface font when it is a family. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Paper text: any face with outlines is set, and says how a PDF may carry it (#4890 F2) A face is admitted for paper whatever its tables lack: - a variable face is set at its default instance, which is what a PDF draws; - its cap height is the one sketch text scales it by (OS/2, else the top of its `H`, else 70 % of the em); - its PostScript name is what a PDF name can hold of its own, else of its family and style, else `Font-` and the start of its id; - without a `post` table it has no italic angle, and a fixed pitch when every glyph advances alike; - its stem width is measured across its `l`, else taken from its weight. A bitmap or colour-only face, with no outlines to fill, is refused by name. `Font.embed` says how a PDF may carry the face, from its embedding permissions: a subset, the whole face (no subsetting), or not at all (restricted, bitmap embedding only, or CFF2 outlines), when the PDF draws the runs' outlines with their text beside them. Which characters a PDF name may hold is now one rule, `is_postscript_char`, that the record's check and sketch share. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Paper text: a font's family, for readers that pick fonts by family (#4890 F2) `Font.family` is the face's own family name: its typographic family (name ID 16), else its family (ID 1), read from the face itself (in a collection, not the first face), without control characters and at most 255 characters; empty when it has neither. A DXF text style picks a font by it; `Font.id` stays the identity, so two faces of one family are still two fonts (@0xlillith on #4890). The name lookup PostScript names use is one function now, shared by both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Fonts: one list of fallback fonts per script, in sketch (#4890 F3) The interface's lists of fonts to try for a script move down to `sketch::fonts::fallback`, so drawings can use the same ones. Each row is a family and the files it's known to be in. The interface reads the files in the order it always did, so what it finds is unchanged. - `CRAFT_FONTS_DIR` joins `font_dirs`, as the interface already has it: `SOLIDCRAFT_FONT_DIRS`, else the pack, then the system's folders. - `font_files` sorts the files within each folder, so a search doesn't depend on how the system lists a folder. - The Windows folder search takes its names from the Japanese list instead of its own copy of them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Paper text: a character its font lacks falls back to a font that has it (#4890 F4) A text on paper was refused for any character its font lacks. Now each such character falls back to an installed font that has it: the script's list in `sketch::fonts::fallback` first, Han by the drawing's language, then the first face in sorted order with outlines and the character. - Runs split by direction, then by font. Every font takes the same em size, on the line's baseline and pitch. A space, a digit or punctuation stays with the run it follows when that font has it; a mark stays with its letter. A font fallen back to is shaped, with the drawing's language. Its nearest style is used: bold or italic isn't refused. - A line its own font has all of is set as it was. - A character no font has is still refused by name. - The last-resort search reads each file's character map alone, stays within `MAX_FONT_FILES`, and keeps its answers, misses too, for the process. - `paper_text_now` is for the window's thread: the search runs on a thread of its own (`font-search`) and it refuses with `FontPending` until the answers are in. `paper_text` searches where it's called. - `paper_fallback_notes` names what fell back, for a drawing's status, from the answers so far: it looks for no font. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
b4a3a9baae |
Wave 45/45: smlib export test: a rounded cube's corners checked on solidcore's spheres (and 3 more)
4 commits, squashed; the originals stay reachable from branch jyviko/merge-main-develop-solidcore (#11360). - |
||
|
|
c5ccb9201d |
Wave 43/45: smlib: rustfmt after the C++ kernel's removal (and 1 more)
2 commits, squashed; the originals stay reachable from branch jyviko/merge-main-develop-solidcore (#11360). - |
||
|
|
28e983e191 |
Wave 39/45: diagnostics.kernel takes a typed contract, as main's other diagnostics commands do
1 commits, squashed; the originals stay reachable from branch jyviko/merge-main-develop-solidcore (#11360).
-
|
||
|
|
d5fd14135f |
Add the native Drawing workspace UI scaffold (#5349)
* docs: plan drawing workspace UI implementation
* assets: add drawing icon drafts for owner review
* feat(ui): add drawing workspace scaffold with shared ribbon and docked settings
Expose planned Drawing tools without introducing drawing documents, paper rendering or CAD behavior. Reuse existing source BOM, properties and flat-pattern services; retain placeholders for unapproved icon drafts.
Validation: full UI, kernel, icon, clippy, wasm and layout checks passed. Full cargo xtask ci reports three geometry failures that also reproduce on unchanged drawings base
|
||
|
|
d01de87552 |
Merge main into drawings (Oct 30)
Brings the 1,270 commits on main since the drawings branch last met it,
including the first drawings landing (#8249), into drawings.
Merged with the drawings snapshot that #8249 landed (
|
||
|
|
f59f6698f1 |
Ribbon drop-downs as a grid of large icons, as an option (#4739) (#5012)
* Ribbon drop-downs as a grid of large icons, as an option (#4739) Preferences > Interface gets two settings, both off by default: - Menus: List | Large icons. With Large icons a panel's drop-down shows its commands as rows of up to five icons at 32 px (twice the list's), keeping the list's group headings and order. The name, shortcut and help are in the tooltip, as on a ribbon button. Arrow keys move across and between rows; a click starts the command; right-click and drag pin it as before. Resting on an icon for two seconds arms a pin in its corner; a quicker click anywhere starts the command, so nothing is pinned by accident. A pinned command shows its pin. - "Leave pinned commands out of the drop-down menus": a drop-down (list or grid) leaves out the commands the ribbon shows as buttons; a panel too narrow for its buttons lists them again, and a menu is never left empty. No icon artwork or colours change. Sketch > Create keeps its family list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Sketch > Create in the large-icon grid: every variant under its family (#4739) With Menus: Large icons, Sketch > Create showed its family sub-menus as plain text rows (Line, Rectangle, Circle, ...), unlike every other menu. It now shows every variant as a large icon under its family's heading, then the other tools under "More". The list keeps its family sub-menus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-icon menus: groups side by side, no footer (#4739) A menu of two or more headed groups (Sketch > Create's families, Model > Modify's Edges, Faces, Bodies, Specialise) puts the groups side by side, each heading over a column of its icons, at most two across. Menus without groups, or one too wide for the window, keep rows of up to five. The pinning note under the large-icon menus goes: the tooltips and right-click menus say it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-icon menus: a separator line between groups side by side (#4739) As between the ribbon's sections: a 1 px line in the theme's border colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-icon menus: at most three rows, growing to the right (#4739) A menu fills five columns, more when that would take over three rows (Model > Create: 7 x 3); in grouped menus a group too big for three rows gets more columns. Only a window too narrow for that wraps into more rows. A test checks every panel's menu of every tab. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-icon menus: group headings centred over their icons (#4739) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drop-down group headings: smaller than the drop-down's header, not in capitals (#4739) Edges, Faces, Bodies (and Sketch > Create's families) were drawn in capitals at font_small, which reads larger than the drop-down's own header (font_caption). They now use font_micro, written as named, in the list and the large-icon grid. Review feedback on #5012. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-icon menus: start a command from the grid at its icon, as main's rows do (#4739) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Ribbon icon grid strings: a pending locale file, with ar, he and vi (#4739) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Large-icon menus: commands in no group stand apart, so a left-out group doesn't move them (#4739) With Convert to Sheet Metal pinned and left out of Model > Modify, its Specialise group went and Delete, in no group, moved under Bodies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
bd1525dcfb |
Expressions: hyperbolic functions sinh, cosh, tanh and their inverses (#9332) (#9334)
* claim: #9332 * Expressions: hyperbolic functions sinh, cosh, tanh and their inverses (#9332) `sinh`, `cosh`, `tanh` and `asinh` take a unit-less number and give one, as `exp` does; `acosh` refuses x < 1 and `atanh` refuses |x| >= 1, worded as `asin` and `ln` refuse what lies outside their domain. They are in the expression help (`FUNCTIONS`, the parameter autocomplete), the hostile-expression fuzz tokens, the parameters and equation-curve guides, and the checking skill's list of functions. Horn flares (Salmon's hypex `cosh h + T sinh h`), catenaries and the tractrix can now be written as they are stated instead of expanded into `exp`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
71bb4f7c98 |
Docs: a beginner's page for equation curves, with pictures and examples (#9282)
A new user guide page, Equation curves, under Sketching: the first curve step by step, Explicit and Parametric explained with tables of rules to type (a line, a parabola, a wave, a circle, a spiral), radians, parameters with the involute of a base circle, building a part (a closed flower curve extruded), editing, and common mistakes. The technical details stay in a short section for scripts and agents. Seven pictures, rendered by `cargo xtask book` from new scripts in docs/book/scripts/equation-*.json (gitignored output, attributed in attribution/pending/docs-equation-curves.md). The Sketching page keeps a short summary and links to the new page. |
||
|
|
92af4a21c8 |
Control channel: a per-request param_check level, applied and confirmed by the app (#9184) (#9185)
* claim: #9184 * Control channel: a request's own param_check level, applied and confirmed by the app (#9184) `engine.execute`, `engine.batch` and `engine.script` take an optional `param_check: "off"|"warn"|"strict"` in their params. The app runs the request at that level through the same `run_checked` path and batch preflight a headless session uses, puts its own level back, and every reply says the level it ran at as `param_check` beside `ok`. A command that switches designs keeps the app's level; any other word is refused. Readings and exports answered on a copy off the window thread keep the level too. `control.capabilities`, answered at once on the control-server thread, lists what the channel does beyond the methods every version has: `{"param_check": ["off", "warn", "strict"]}`. An older app answers "unknown method". The MCP bridge asks it once per connection. When the app lists the level, every call and batch goes with it and the app checks against its own commands; otherwise the bridge checks before sending, as #9073 does. At strict, a reply that doesn't say the app checked the call is an error, and the next calls are checked by the bridge. Also: `add_param_warnings` takes the reply by `&mut`, and `with_param_check` sits beside `Remote::new`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Control channel: a design-switching command is checked at the request's level Review fixes for #9184. `component.edit_in_place`, `component.finish_edit`, `view.next_tab` and `view.previous_tab` ran at the app's own level and echoed `off`, so a misspelt call went through at strict. They now have their parameters checked at the request's level before they run (and still run at the app's own level, as each design keeps its own), and the reply echoes the requested level. The MCP bridge at strict now requires the echo to match its level, not just to be there. `param_check` is read only by `engine.execute`, `engine.batch` and `engine.script`; other methods ignore it. The off-thread test now shows the design change computing off the window thread before it is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
991082fbaf |
Typed contracts for the construction axes, points and planes (#2829) (#7750)
* claim: #2829 (construct.* contracts) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Typed contracts for the construction axes, points and planes The 19 construct.* commands get input contracts and FeatureResult as their output, with shared types for references, planes, a plane or face base, an axis line and a construction plane's name, each calling the reader its handler uses (split out of geo, plane_param, the face capture and named_axis_line). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
94b399e98f |
Preference: keep the view when a sketch opens; Look At faces it on request (#9188) (#9190)
* claim: #9188 * Preference: keep the view when a sketch opens, Look At faces it on request (#9188) Preferences > Sketch > "Turn to face a sketch when it opens" (on by default, so nothing changes unless it is turned off). Off: creating or editing a sketch leaves the view as it is, a sketch started on a face no longer re-centres on the picked point, and Look At (the palette button, Ctrl+8) still turns to face the sketch. The control channel sets it as `ui.prefs {"sketch_open_look_at": false}`. Changes a default: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
6e4e94a8a8 | Regenerate generated files and fold pending files [skip ci] | ||
|
|
ae2a5b2c0d | Regenerate generated files and fold pending files [skip ci] | ||
|
|
6a2bdbf144 |
Bill of materials: virtual components and a typed quantity (#3874, P6) (#9226)
`bom_quantity` is a part property of any component: what one placement of it means, an expression evaluated with the design's parameters (pieces without a unit, else a length). A row's `quantity` is what its placements add up to, with `quantity_unit`; pieces beside a length, or a quantity that no longer evaluates, give none and say why. A quantity is a parameter user: a rename follows it, and a parameter it uses can't be deleted. The CSV gains a `quantity` column at the end. `virtual` marks a component with no geometry (grease, paint, a label): listed with its quantity, weighing nothing. It can't be the active component, and one check after each undoable outermost command refuses any change that puts a body, a sketch or a placed component into one, whichever command made it. A file a damaged virtual component already fills opens, and other changes go on. Mass, volume and area quantities wait for the expression units of #4811. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
81cbcef31c |
Sketch: add construction-line toolkit (#8239) (#8679)
Co-authored-by: railconstr <335242259+railconstr@users.noreply.github.com> |
||
|
|
413c145c56 |
Bill of materials: stable item numbers, saved with the design (#3874) (#7779)
* Bill of materials types on one occurrence (#3874) An occurrence can count in the bill of materials as another type than its component, for that occurrence only: `bom_type` on the occurrence, set with occurrence.bom_type (one undo step; it changes no geometry, so nothing is evaluated). An occurrence without it counts as its component, so older designs need no step; the format change is one pending file (occurrence-bom-type.rs). assembly.bom counts each placement by its own type: a purchased placement of a kit part is one row without the kit's contents, a phantom placement lists what it holds where it sits, and one component placed as two types gives a row for each. Paste New keeps the types; Paste Instance counts as the component. The compact list and the locales (en, de, ja, ko, zh) carry it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials rows: material, and mass when asked (#3874) Every row of assembly.bom (and every part folded into a row) names the physical materials of its definitions' own bodies, each once, with "; " between several. An unassigned body's is Default. A subassembly or an empty component has none of its own. With mass: true each row also carries mass_g, what one of its parts weighs as the row stands for it, so the rows of any list add up to the assembly once: - at the top level, all it holds; - in parts only, its own bodies, because what it holds is listed beside it; but a folded part's row is the kit (all it holds), a purchased part is all it holds, and an inseparable one all but its purchased parts; - a reference placement adds nothing, nor does anything inside it. It is null when nothing in the part has a body, a body can't be measured or measures a negative volume (inside out, such as an open mesh facing the origin), or the definitions sharing the row's part number weigh differently. Measuring runs the kernel, so it is asked for: the Bill of Materials window re-runs assembly.bom after every change and doesn't ask (0.2 ms without mass, 135 ms with it on a bolted bracket). Placements are rigid, so each definition is weighed once however often it is placed, and each body is measured once with measure_mass (the same volume as measure, without the topology counts' tessellation). A body's material and its density now follow one rule: Document::body_material (new, beside Document::density) is the material its mass is worked out with, named as the library or the design spells it: the assigned one when it is known and has a usable density, else DEFAULT_MATERIAL (new, beside DEFAULT_DENSITY). Document::density is that material's density, with the same values as before. So a damaged file's unknown or broken material reads as Default, as it weighs, and a hand-edited "steel" reads as Steel. The bill of materials uses both; the other places that read a body's material or spell "Default" (component.properties, inspect and others) move to them in a separate cleanup PR for #4783, which brings the same two functions to main. Document::body_owners returns each body's owner in order, so the rows find their bodies in one pass. It no longer repeats the owner rule: model_body_component, legacy_body_component and body_owners go through the rule written once, given how to find a feature's component (the timeline for one body; body_owners still builds its map once). The CSV columns are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Physical properties: a reference placement adds nothing (#3874) component.properties and occurrence.properties now leave out a placement that counts as a reference in the bill of materials (its occurrence's bom_type, else its component's: a fixture shown for context), and everything inside it: no body entry, mass, volume, area, centre of mass or bounding box, and it isn't in placement_count. That is the bill of materials' rule, so a reference part is in neither the parts list nor the assembly's mass, and every bill of materials list still adds up to the assembly's Properties. The placement asked for counts whatever its type: a reference fixture's own Properties still measure it and what it holds, as a component's own bill of materials lists what it holds. Only the totals change. The placements walk that face picking, sketches on placed faces and the occurrence path lookup use stays complete, so a reference fixture's faces can still be picked. The type an occurrence counts as is now written once: Document::occurrence_bom_types (new, beside BomType) gives each occurrence's type in their order, looking each component up once. The bill of materials, occurrence.bom_type's reply and the physical properties use it, in place of the two copies in the bill of materials. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: one module of typed rows in doc (#3874) The bill of materials now lives in solidcraft_doc::bom as the typed rows of a bill of materials view. assembly.bom's JSON and CSV and the Bill of Materials window present those rows; parts lists, item numbers and balloons are to read them too. - list(tree, request) walks the placements below the assembly once. One rule (classify) decides for each placement whether it is counted in a row, how what it holds is walked and what its row stands for when weighed, from the type it counts as, the view and the component's shape. "An inseparable part's purchased parts are listed beside it" is written there once, and the mass walk reads it. - A row carries its identity (RowKey: the rows it is listed under and its lowest member, a component and the type it counts as), its item, count, each, the placements it counts by occurrence path, what it stands for (Holds), its part properties, materials, mass and children. - Mass is a Mass: not asked, grams, or unknown with the reason (nothing to weigh, unmeasurable, the definitions differ). Each definition is weighed once for each kind of row and kept in the Tree. The assembly's total is weighed from the design the same way, not added up from rows, so it is known even when a row's definitions weigh differently. - Bom::row_of(tree, path) finds the row a placement is counted in (a nut inside a purchased bolt is in the bolt's), or says why there is none: no such placement, a reference, or no row of its own. - rows_deep() lists each row followed by its children (1, 1.1, 1.2, 2); Request::default() is assembly.bom {}; View and Group carry the commands' words. What changes, beside that the output is otherwise the same: - One placement budget (100,000) and one depth limit (64 levels) cover the whole list. A folded part's contents used to be walked under a budget of their own, so a design past the limit was listed with group_children; it is refused now, as it already was without it. - Kits are compared by what they hold (part, type and count), not as text: a part without a part number whose id is 12 no longer reads as a part numbered "#12", nor a part numbered "P×1\nQ" as a P and a Q. Kits holding the same parts through different definitions (two bolt definitions, each with its own nut definition of one part number) are one row, and its children count every bolt's placements, where they showed the first bolt's definitions only. - A folded part's row always stands for all it holds. It used to stand for its own bodies when nothing inside it was listed, which differs only for a phantom with bodies of its own inside it. - An inseparable part's row in parts only names the material of what it stands for (its steel blocks read Steel) where it was blank, and a subassembly whose parts share one material names it at the top level. - A damaged file's component placed inside itself is refused at the first level too, since naming a row's material walks inside it, as weighing it already did. - A row's body count saturates, so a compact assembly with more placed bodies than a count holds (a block placed 2^64 times) is weighed instead of overflowing. Materials follow Cameron's rule for a row that stands for an assembly (C3, 2026-10-23): a part's row lists its own bodies' materials, as before; a row that weighs what is placed inside it (a subassembly at the top level, a purchased or inseparable part with parts inside it, a folded part) names a material only when everything it stands for shares it, else none. A frame of steel and aluminium parts reads blank, and a bought bearing of steel parts reads steel. One walk (adding) decides what is placed inside a row's part for both its mass and its materials, and each definition's materials are read once. Components are looked up by id once per list; they were searched for each row and each kit. The window reads the rows directly instead of running assembly.bom after every change, so it no longer adds an entry to the crash journal each time it refreshes. The engine keeps the command, its parameters and the files. Tests check rows. The module's own: an empty assembly, one part, a row named by its lowest member through part-number edits, one part number of two types, a phantom in a phantom, a reference at the root and its own list, the 64-level limit, the budget hit exactly with and without folding, kits told apart by what they hold and the types they hold it as, a folded part's contents not folded again, row_of for every type, every placement in one row or none, a damaged file's missing component and cycle, the mass agreement rule (moved from the engine with row_mass) and what adds to a row. In the engine: the material rule for every kind of row, the JSON against the rows for every option, why a mass is unknown, the total against component.properties in every view, what a row stands for and its placements, and the overflow. The assembly.bom tests from #1888 on pass unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: structured, all levels (#3874) P4: `assembly.bom {levels: "all"}` lists the assembly's direct contents, each subassembly a row with its own rows listed under it, to any depth (items 1, 1.1, 1.1.1), each child counted `each` per one of its parent and `count` in all. - `doc::bom::View::AllLevels` goes through the one rule (`classify`) the other views use: a normal subassembly is a row holding what it holds, listed under it as all levels again; a phantom has no row and what it holds joins the level it sits in, merged with the same parts there; a purchased part is a row with nothing under it unless `show_purchased_contents` (Request::purchased_contents) asks for its contents; an inseparable part is a row with nothing under it; a reference counts nowhere. - The rows under a row are the folded-part rows already built (kits), so subassemblies with one part number and different contents are different rows, and no second nested builder is written. All levels gathers every level as it gathers the first; a folded part's contents are still gathered by part number in the other views. - The JSON and CSV already nest to any depth; `show_purchased_contents` is echoed with the other options. Each row's mass still adds up to the assembly at the top. Tests, worked by hand: four bolts and a two-bolt subassembly placed twice (8 bolts in parts only; in all levels the subassembly twice with 2 bolts each, 4 in all, beside the top's 4); three levels deep (2 x 3 x 2: counts 2, 6, 12, items 1, 1.1, 1.1.1, the CSV in that order); each type (a phantom's bolt merged into its subassembly's, purchased with and without its contents, inseparable, reference); two subassemblies of one part number with different contents as two rows; `levels: "deep"`, a flag that isn't one, and an assembly over 100,000 placements refused. D's test that a folded part's contents are listed as parts only lists them now names the two views it holds for, and checks all levels' nested rows (the subassembly inside the folded part a row with its own rows). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: parts with one part number are one row only when they hold the same to any depth (#3874) The kit compare looked one level down: two parts with one part number whose contents differed two levels below merged into one row, and the children that only one of them held came out at each 0. Each placement now has a kit, a number interned from what it holds (each child's part, type and own kit), built in one pass from the last entry back: an entry's children come after it in the walk, so their kits are known first. Rows are told apart by that number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: a phantom's own bodies are a row of their own (#3874) A phantom had no row, and its own bodies were weighed in the assembly's total but in no row, so the rows didn't add up. Its own bodies, when it has any, are now a row of the phantom's type that stands for them alone, as a subassembly's own bodies are in parts only; what it holds still counts where it is placed. Inside an inseparable part in parts only, nothing changes: the part's row weighs them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: the assembly's total mass in assembly.bom (#3874) `list` weighed the assembly as a whole (from the design, not added up from rows) and the command dropped it. `assembly.bom` now gives it as `total_mass_g`, and so does a JSON file it writes, only when `mass: true` (null when it isn't known). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: all levels as JSON lists its rows one after another (#3874) All levels nested each level's rows in the row above it: two JSON containers a level, so the 64 levels a list may hold came to 129, past the 128 that serde_json (and so the MCP server and the control channel) reads. In all levels `assembly.bom` now gives the rows one after another, each followed by the rows under it (items 1, 1.1, 1.1.1), as its CSV does; a row listed under another still reads its item as text and gives `each`. A folded part's contents, one level, stay inside its row as `children`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: a row whose definitions weigh something and nothing differs (#3874) A row gathering a definition with bodies and one with none (one part number on a steel block and on an empty assembly) said there was nothing to weigh. Their masses differ, so it says so; only a row where none of them has a body has nothing to weigh. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: definitions agree on a mass within the measurement accuracy (#3874) A row's definitions had to weigh the same to 1e-9, but the kernel measures a volume from tessellations, and one part modelled another way is tessellated differently: a pin made as a cylinder and as a revolved rectangle measured 1.05e-5 apart, so it read as two masses. The bill of materials now has its own `bom::MEASURE_ACCURACY` (1e-4: ten times that gap, with its doc on what it follows: the kernel's `measure` gives one solid's volume to about that, relative, however the solid was modelled), and a row's definitions agree within it. A pin 0.1% wider (0.2% heavier) still differs. The kernel is unchanged: its work is paused while it is being replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials guide: all levels, purchased contents and this round's changes (#3874) The guide had no `levels: "all"` or `show_purchased_contents`. It now has both, with how all levels tells subassemblies apart and lists its rows as JSON, a phantom's own bodies as a row, the assembly's `total_mass_g`, and definitions that agree on a mass within the measurement. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: materials are bodies', as the code says (#3874) The rule stays as built (a row that stands for an assembly names a material only when everything in it shares one), but its comment and the guide said a material set on a component is set on all its bodies. `material.assign` takes bodies only: a component has no material of its own yet. The module notes now say so, and where component materials will join the rule. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: a body's volume measured once with the evaluated model (#3874) `assembly.bom` measured each definition's bodies anew for every list, and `component.properties` measured each placed body again. A body's measure depends on nothing but the body, so the evaluated model keeps its volume (`ModelBody::volume`, shared by clones, as its outline box is), and both commands weigh that one measurement: the same grams to the bit. A body offset scales it by the size of its determinant (`mat_volume_scale`) instead of copying the B-rep to measure it. The physical properties still measure the placed body for its area, centre, box and counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: row keys, what rows stand for, members and row_of stay in doc (#3874) Nothing outside `doc` reads them yet: `RowKey`, `Unlisted`, `Holds`, a row's `key`, `stands_for` and `members`, and `Bom::row_of` are crate-private until item numbers and balloons need them, so they can still change without breaking another crate. The engine test that read them is covered by doc's own (`row_of_finds_the_row_a_placement_is_counted_in`, `every_placement_is_in_one_row_or_none`). `Unknown` stays public: `Mass::Unknown` carries it, and a public enum can't carry a private type. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Assemblies: one fallible placement walk for totals that cover the whole assembly (#3874) The physical properties walked the placements twice in their own code, with the 64 levels and 100,000 placements written as literals, and the bill of materials gave the same refusal in words of its own. `Document::placements_below` is that walk once, from any placement, refusing what is past `MAX_PLACEMENT_DEPTH` or `MAX_PLACEMENTS` (never walking it in part), and both use its refusal: "the assembly is deeper than 64 levels or has more than 100000 placements". The physical properties now allow 100,000 placements below the start, as the bill of materials does (they stopped at 99,999). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: the same rows for less work (#3874) - Part numbers are borrowed from their definitions (`Part<'a>::Number(&'a str)`) instead of copied for every placement grouped and every kit. - A row's members, the placements' paths, are built only when the list is asked for them (`list_with`, for item numbers, balloons and `row_of`): the walk then keeps paths and moves each into its one row instead of copying it. A list made for its rows alone builds no paths at all. - `row_of` reads an index of member paths, built once with the list, instead of scanning every row's members for each placement asked about. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * occurrence.bom_type: input and output schemas (#3874, #4035) `OccurrenceBomTypeParams` takes the occurrence (an id or a name), its path when given and the type (one of the five, `same_as_component` or null), each read by the handler's own reader: the path through `cmd::path_ids`, split out of `occurrence_path`, the type through `cmd::occurrence_bom_type`, split out of the handler. The handler builds its reply from `OccurrenceBomTypeResult` ({occurrence, bom_type, effective, changed}). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials tests: all levels presented at every depth, and its caps (#3874) - `assembly_bom_presents_the_typed_rows` now covers all levels (with mass, purchased contents and by definition) and the total mass, on a three-level design with a purchased gearbox holding a gear, comparing every field of every row down to 1.1.1.1. - All levels lists 100,000 placements and 64 levels, and refuses 100,001 and 65. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: static materials, typed in for a component (#3874) A value the bill of materials works out can now be typed in by hand for a component (a static value, Cameron's choice of word). Material comes first: `component.metadata.set {component, material}` takes any text up to MAX_TEXT_BYTES, and `component.metadata` reads it back as `material`. It is shown wherever the component is listed, at every level and in every view, over the materials worked out from its own bodies or an assembly's blank, and the row carries `static: ["material"]`. Clearing it (empty text or null) brings the worked-out material back. It is kept the way `bom_type` is: a part property in the component's metadata (`material`), so there is no format change and older releases keep it. `PART_ONLY` names both keys in one place: Design Properties doesn't list them and `document.metadata.set` refuses them. Another tool's non-text value is kept and reads as no static material. The bill of materials says one thing about the part everywhere: a static material also stands for the part in the one material an assembly around it shares (two parts typed in as S355 make their frame read S355; S355 beside plain steel reads blank). A phantom's row is its own bodies, so its static material names those only, and what it holds keeps its own. A row gathering several definitions takes the first one's that has a static material, as its description does. It only names: mass still comes from the bodies. The CSV gets a `material` column at the end, so the other columns keep their places: what the row shows, the static material or the worked-out ones joined with "; ". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Bill of materials: stable item numbers, saved with the design (#3874, P5a) A numbered parts list keeps each row's item number under the row's key in `assembly.item_numbers`, so a row keeps its number when others are added, removed or reordered. New rows take the next number after the highest at their level; Renumber restarts a view in list or item order and leaves locked rows alone; duplicates are marked; numbers change no geometry and are part properties in the undo history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a66a517c40 |
Persistent naming: a proposal from a second implementation (#7990)
* Persistent naming: a proposal from a second implementation A design proposal for the persistent-naming failures still open (#4341, #5198, #5308, #5857, #5858, #5860, #5861), from building persistent naming in Form Studio: fall back by lineage, never by geometry alone; a suppressed maker is not a lost reference; names saved as structured one-level values; ordinals from the feature, not the kernel's face order; split picks resolve to all pieces; body identity apart from the display name. Documentation only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Persistent naming proposal: body identity follows the #5308 plan Change 6 now agrees with bluecmd's plan on #5308: an id minted from next_id and stored per feature slot, better than the derived identity the first version proposed. It adds that a stored id is only as stable as the slot assignment, which today follows each lump's least face name, and gives the lineage signature from the #5308 discussion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f3ee34d372 |
English: American spelling throughout the base catalog, British only in English (UK), and "1 body" (#9085) (#9194)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
a09c24e9bf |
Drawing dimensions: a circle's or an arc's radius and diameter (#4896, W20a) (#8959)
* Drawings saved inside the design's file, in both forms (#3890) K3b of the drawing storage plan, after #7098: a drawing lives in the file of the design it draws (bherbruck's proposal, Cameron's choice), so the separate drawing file goes. - `Document.drawings`: the design's drawings, each its own record of the drawing family (`solidcraft-drawing/N`), each behind its own `Arc`. Every reader and writer of a design now carries them: save, Save As, autosave and recovery, crash copies, the CLI. - A design container keeps each drawing in its own entry, `drawings/<id>.json` (Deflate), beside `document.json`, which holds nothing of them: its bytes, and the model cache keyed to them, are the same with drawings or without. A plain JSON design keeps them inline under `drawings`. - Each drawing is read by its own family's rules, taken out of the design's JSON before the design's own checks. One a build can't read (a newer number, a change it doesn't know, damage) refuses the whole design, never dropped. So does an entry under `drawings/` that isn't a drawing's, one whose record has another id, drawings inline in a container's `document.json`, two drawings with one id, or more than 1,000 (counted before any is read). A design is never saved with two drawings of one id. - Drawings are listed by name, then id: no stored order, nothing to merge when two people add drawings. - A drawing has an `id`, unique among its design's drawings (`Drawing::new` takes the ids already taken). Its source is the design it is in: at most one, an id and no path. - One pending design change, `drawings`: the last release refuses a design saved with it, so it never opens one and saves it without its drawings. One pending drawing change, `in-design`, for the record's new shape. - Gone: `read_drawing`, `read_drawing_file`, `write_drawing_container`, `file_kind`, `FileKind`, `DRAWING_EXT` and `is_drawing_path`. `read_document`, `read_design` and the container writer are as they were before #7098, with the drawings added. Tests: a design's drawings round-trip through both forms byte for byte, with `document.json` and `container.json` the same with drawings or without; their order; every refusal above; a drawing's properties kept as other tools wrote them while a sheet's number out of range is refused; the drawing's id among its objects', a new drawing's id none of the design's; a design with two drawings of one id not saved; mutation fuzz of a design with drawings in both forms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing commands on the design's own drawings, each drawing its own undo (#3890) K4' of the drawing storage plan, on K3b (a drawing lives in its design's file): make, list, open, close and delete a design's drawings, and read and change the open one's settings and properties. It replaces #3891's drawing sessions, which held a drawing as a file of its own. - `drawing.create {name?, language?, units?}` adds a drawing of the design (one ISO A3 landscape sheet with the ISO border, the design its one source) and opens it; refused on an empty design. `drawing.list`, `drawing.open {drawing}`, `drawing.close`, `drawing.delete {drawing}`. `drawing.inspect`, `drawing.set`, `drawing.properties`, `.properties.set`, `.properties.remove` work on the open drawing. Labels in en, ja and zh. - The open drawing is the session's (`Session::open_drawing`, kept in checkpoints), and the part gate keeps the parts apart: a command says the part it works on, the model unless it says otherwise (`.in_drawing()`, `.any_part()`). While a drawing is open the model's commands wait ("a drawing is open: close it to change the model"); the drawing's run only while one is. Files, recovery, undo and redo, checkpoints, selection, diagnostics, the command list and choosing a drawing work either way. `CommandSpec::allowed` is the one check running a command, running it inside another and listing it make. - `Session::drawing` and `Session::edit_drawing` are the open drawing's one way in: an edit is kept when it changed something and still keeps the record's rules, in a new design whose other drawings are shared. - A change that only touches drawings evaluates nothing: `Document::differs_only_in_drawings` joins the render-only path, as render settings and part properties do. A model edit shares every drawing, so it pays nothing for the check. - Undo per part: a step records the part it changed (`Snapshot::part`: a drawing, or the model and its list of drawings). Undo and redo go through the open part's steps only. A drawing's step puts back only that drawing; the model's step puts back the design as it was, keeping each drawing it had as it is now. Making and deleting a drawing are the model's steps. A new step ends what could be redone in its own part only. `Snapshot::of` is the one place a step is taken. - An agent's message that changed several parts keeps their steps apart, rather than merging them into one step that would undo only one part. - `cmd::metadata`'s rules for reading, setting and removing properties are written once (`reading`, `setting`, `removing`) and used by the design's and the drawing's commands. - `drawing::sort` is the one place the drawings' order (by name, then id) is kept; `Document::put_drawing` adds or replaces one in that order. Tests: making, listing, opening, closing and deleting; what making one refuses, at the cap too; the gate both ways, inside another command and in the command list; drawing edits as undo steps that evaluate nothing over 100 rounds; undo and redo per part with model and drawing steps interleaved; redo ended per part; the order by name, then id, after renames; delete and its undo; refused changes; checkpoints; save and open in both forms; properties; hostile values for every command that runs with a drawing open, from the registry; a fuzz of the steps in any order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Unknown-command tests: the closest id first (#3890) drawing.open now joins doc.open in the suggestion for `file.open`, as the rule says: both end in the same word. The engine's and the CLI's tests check that doc.open comes first and let other close ids follow, as the `solid.extrud` line beside them already does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Model bodies: their extents seen on a plane, for a drawing view's size (#5866) ModelBody::mesh_bounds_in(plane) is the box of a body's mesh in a plane's own coordinates (Plane::to_local), beside mesh_bounds and in the same way: a body meshed already is measured from its own mesh, and a placement of a part from its definition's shared mesh, with the placement folded into the plane's two axes once (two dot products a point), so no moved copy is made. W07's drawing.view measures a view's extents with it. Tests: a 100 x 50 x 20 block seen on the three principal pairs of axes and from an isometric corner (150/sqrt(2) by 190/sqrt(6), worked by hand), a plane away from the origin, and every placement in the shared-mesh test against its moved mesh, still without a moved copy, and the same again from its own mesh once it is meshed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: projected views in first or third angle, lined up with their parent (#4816, W17a) W17a's second slice: the principal views derived from a base view. - `ProjectedSide` (left, right, above, below): the side of its parent a projected view sits on. - `projected_basis(parent, side, projection)`: in third angle a view shows the side of the model it sits on (one to the right looks from the right), in first angle the side away from it (ISO 5456-2). It's the parent's basis turned a quarter turn about one of its paper axes, through its origin, with no -0. From the front view it gives exactly the named views: right, left, top and bottom in third angle; left, right, bottom and top in first. - `ViewFrame::aligned(side, projection, centre, position_mm)`: a projected view at its parent's scale, lined up with it, so it moves only along the free axis. - The projection is S2's `cell::Projection`, the value the title block's symbol reads, so a drawing has one first-or-third setting. Tests: the front view's projections are the named views in both angles, with no -0; from every named view and a leaning one, a projected view looks from its side and keeps the parent's other axis and origin, four turns or a turn and its opposite bring the parent back; a box's corners line up across aligned views in both angles; hostile parents, centres and positions refused. Corner (isometric) projected views wait for one convention check on first angle. The derivation graph (projected from, aligned to, cycles refused) moves to the view record in the drawing file, where the view ids live. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: measurement on view frames (#4896, W19a) `geom::drawing_measure` is what a dimension on a view reads, in model mm or radians, from model points and edges and the view's basis: one rule for every dimension. The view's scale and place on the sheet never enter. - `linear`: horizontal and vertical along the view's paper x and y, aligned as the view shows the segment, or along a direction picked on the view; a true reading is the segment's own length (aligned only). - `angle`: from the view's horizontal or vertical, a model axis as the view shows it, or another edge, from 0 to π as each line is oriented; a true reading is between model lines. - `foreshortened` says a measured line isn't parallel to the view's plane. - Refused with a reason: points that aren't finite or are past 1e12 mm, an invalid basis, a zero-length segment, a line seen end-on, a zero direction, a zero reading, parallel lines, and a true reading where there is none. `drawing_view`'s `PARALLEL` and `require` are shared. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * drawing.view.snapshot: a view's scale, place and boundary on its sheet (#5866, W07 V2) The first slice of W07: one orthographic view of the design, worked out and returned as data, before any line is drawn, so scripts can lay views out by their boundaries. - `drawing.view.snapshot` takes the view's settings, the sheet it goes on (A3 landscape at 300 dpi when not given) and where its centre goes (the sheet's centre when not given). With no scale it takes the largest ISO 5455 scale at which the view, centred there, lies inside the sheet's drawing border. It returns the resolved view, the scale (and whether it was fitted), the extents on the view's plane, the boundary on the sheet and what it was drawn from. Read-only and not undoable; it opens nothing and saves nothing. - Every body the design places is in the view, wherever its occurrences place it; the browser's eye counts for nothing (`engine::drawing_view::extents`, from P2's `mesh_bounds_in`). A design whose assembly walk stops at its limits (deeper than 64 levels, or past 100,000 placements) is refused rather than drawn with parts left out: `Document::placements_complete`, beside the walk, says whether every placement it holds had its occurrences walked. - `doc::drawing_view::ViewSettings` is the view's one parameter set: the view (named, or a direction and an up), the scale, the two visual styles, the reference settings and tangent edges. A saved view (W12) keeps the same fields; no scale is written as none, not null, so what it writes reads back as the snapshot's parameters. - `render::VisualStyle` names the five styles: visible, visible_hidden, shaded, shaded_visible and shaded_visible_hidden. The shaded ones are refused as not supported yet until the drawing renderer (#5624). - The command has a typed contract (input and output schemas). Its handler reads the contract's own type through `schema::read`, in the contract check's own words, so the check's levels give one message for one mistake and an unknown field is refused by name. The reply includes the sheet, its defaults filled in. Tests: the 100 x 50 x 20 block's boundary in the front, top, right and isometric views at 1:2, worked by hand; the fitted scale on A3 and A4 and near the border; scale, position and margin; the browser's eye ignored; the design untouched; each hostile setting refused; the walk's depth limit one level either side; the contract's keys the same as the settings' own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views in the drawing record: base and projected views, and the projection (#4816) E4 of the view-boundary plan, with #4816's derivation graph (plan on #4816). A drawing now saves its views, as one pending drawing-format change (`drawing/format/pending/sheet-views.rs`): - `Drawing.views`: each view an object of the drawing, on one of its sheets. A base view draws a source from a direction (`ViewDirection`, as `drawing.view.snapshot` takes it); a projected view sits on one side of its parent. Its place is a point of the sheet, or, lined up with its parent, only how far along the free axis, as the other comes from the parent. A projected view's source and basis come from its chain, so neither is saved. - `ViewOverrides` beside V2's settings: what a view sets itself, each setting on its own (scale, display style, reference style, reference parts hide the model, tangent edges); none is written when left out. Each view has its own reference margin. - `Drawing.projection`: first or third angle (S2's `cell::Projection`). Third angle is the type's own default (Cameron, 2026-10-26), for a drawing that doesn't say and for a new one until a template or a preference chooses. - The rules, checked on reading and after every edit: a view is on one of the drawing's sheets; a base view draws one of its sources, has a scale and a direction that makes a view; every parent is a view, no chain of parents loops, and a chain is at most 32 views; a lined-up view is on its parent's sheet; a place is on paper; at most 4,096 views. A view's id is the drawing's, never another object's. - `Drawing::resolve_view`: one rule for every reader. It turns the base view's basis down the chain (`projected_basis`, in the drawing's projection) and takes each setting from the nearest view that sets it, then the default, saying who gave it. - geom's `is_paper_coordinate`: the one rule for a view's place on paper, now used by `ViewFrame::new` and the record. The record tests' seed drawing has views, so its round trip and mutation tests cover them; their unknown-field examples no longer use `views`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Engine tests: one temporary folder helper and the schema check for every test module (#4816) The view tests use the drawing tests' temporary folder and the schema tests' `conforms`, which were private to their modules. `Folder` moves to the engine's test module and `conforms` is open to the crate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: inspect and edit a saved view (#4816, W17c) E5 of the view-boundary plan (plan on #4816): what the view's window reads and writes, for the views of a drawing (E4). Both are drawing commands with input and output schemas, and neither evaluates a model. - `drawing.view.inspect {view}`: its kind (base or projected), sheet, source, parent and side, its direction as resolved (named only when it is exactly a named view), its place, and each setting as {value, from, parent?, set}: where the value comes from (the view, a parent by id, the default) and what the view saves itself. A projected view says whether its display settings follow its parent (`linked`: true, false or "partly"). `choices` gives the display styles and the preset scales, so a window keeps no list of its own. - `drawing.view.edit {view, set}`: one undo step. Each setting takes a value, or null to follow the parent (a base view keeps a scale); the margin, a base view's direction and the place change too. `linked: false` saves the display settings at the values they have, `linked: true` clears them so they follow. The reply names the fields changed and the views projected from it whose settings changed with it. The record's rules hold after every edit; an edit that changes nothing is no step. The contract test keeps the edit's fields and the record's (ViewOverrides) one list. The view's boundary comes with the next slice, which works out where each saved view sits on its sheet from its design's extents. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: where a saved view sits on its sheet (#4816) `Drawing::view_centre` gives a view's centre on its sheet: its point, or, lined up with its parent, its own place along the free axis and the parent's centre across it. The free axis is one rule on `ProjectedSide` (`along`, `on_free_axis`), which `ViewFrame::aligned` uses too. `engine::drawing_view::on_sheet` puts a saved view on its sheet: its design's extents on its plane, grown by its reference margin, at its scale, round its centre. The snapshot and saved views share `measure` (refusing an assembly past the walk's limit or one with no bodies) and `boundary`. With nothing to measure (the design has no bodies or places more than a view walks) a view gets a 20 x 20 mm placeholder round its centre. A view's source is the design the drawing is in, so the session's own model is what it measures. `drawing.view.inspect` adds `boundary_mm` and `extents` (status `placeholder` and why, when there is nothing to measure). It still evaluates no model. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Projection of new drawings: a preference, and drawing.set takes the projection (#4816) A drawing's views follow its projection, first or third angle (third when nothing chooses). `drawing.inspect` now shows it and `drawing.set {projection}` changes it as one undo step. Preferences > Design gets a Drawings section with "Projection of new drawings" (First angle / Third angle). A new drawing takes it as a new design takes "Units of new designs": the app sets it after `drawing.create`, with no undo step of its own. A saved value this version doesn't know reads as third angle; asked for over the control channel, it is refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing sheets: one edit, one lookup, a border that lays out, and schemas (#3875) The sheet commands change the drawing through `Session::edit_drawing`, so the same settings or order again leave no undo step, and answer from one reply type. A sheet is looked up by id or name on `Drawing` (`sheet_index`), where later commands that name sheets find it too. A sheet's border must lay out on it: the record's check runs the border's own layout, the one rule drawing uses, so a 15 x 15 mm sheet with the ISO border is refused when it is made, not when it is drawn. `excluded` must be an object of `count` and `print`: a mistyped key changed nothing unseen. A new sheet documents nothing and leaves nothing out unless told, and the add's parameter documentation says it takes `documents` and `excluded` too. The drawing's properties sit behind an `Arc`, as its sheets do, so an undo step shares them. Each sheet command has an input and an output schema (#4035). A flattened part of a contract reports its keys as the parent's own (`schema::flattened`, which `FeatureCommon` now uses). The tests add the missing cases: 1000 sheets and the 1001st, `after` by id and unknown, the resolution's bounds, a name two sheets share, the same order and settings again, an undone delete in place, and the add's reset of what it documents and leaves out. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing ids: one id source per session, its seed kept for replays (#3890) The drawing commands drew ids from a fresh random source each time, so a crash replay of a drawing could never find what an earlier command made by its id. A session now has one id source (`Session::ids`), kept across a file change, from which `drawing.create` draws its drawing's, sheet's and source's ids: seeded alike and run alike, a session gives the same ids, and a failed command draws none, so a replay of what succeeded draws the same. The crash journal keeps the seed at each base and `replay.json` carries it, which `solidcraft-cli crashes replay` seeds the replay with (`Session::seed_ids`). Tests: two sessions seeded alike make a drawing with the same ids, another seed other ids. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing sheets: add, change, reorder and delete a drawing's sheets (#3875) W18 S4: the sheets of a drawing, on the drawing session K4 made (#3890). Each command is one undo step of the drawing and names a sheet by its id or, when only one has it, its name. - `drawing.sheet.add {after?, name?, size?, orientation?, dpi?}`: a new sheet after the one named (the last when not), taking that sheet's paper, border and resolution, with a name of its own ("Sheet 2") and nothing documented yet. - `drawing.sheet.set {sheet, name?, size?, orientation?, dpi?, documents?, excluded?}`: one setting at a time, the rest kept; `documents: null` clears what it documents, and `excluded` changes only the flags it gives. - `drawing.sheet.reorder {sheets}`: every sheet's id once, in the new order. - `drawing.sheet.delete {sheet}`: a drawing keeps at least one sheet. - Every change goes through the drawing's own rules (`Drawing::validate`), so what a saved file would refuse is refused here. Changing a sheet's paper changes only the paper: regrowing its border and moving what is on it is S5's. Labels in en, ja and zh. Tests: a new sheet copying the paper it follows, after a named sheet too, and undone; each setting changed with the others kept; nine refusals (resolution, sizes, orientation, name, an unknown source, a flag, an unknown sheet, no sheet named) with the drawing unchanged; reordering, and four orders that aren't every id once; a name two sheets share refused in favour of the id; the last sheet kept. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing sheets: a new sheet's id from the session's id source (#3875) `drawing.sheet.add` draws its id from `Session::ids` (#3890's one id source per session), so a crash replay of "add a sheet, then change it by id" finds the sheet: seeded alike and run alike, a session adds sheets with the same ids, and a failed add draws none. Tests: two sessions seeded alike add the same sheet ids, another seed others, a failed add none. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: move or copy a view to a sheet (#4816) `drawing.view.move_to_sheet {view, sheet, position_mm?, break_alignment?}` puts a saved view on another sheet as one undo step, keeping its id, direction, source and settings. The views lined up with it go with it and keep their places beside it; a view placed apart stays. Without a position it keeps its place. A view lined up with its parent moves only with `break_alignment`, and is then placed apart: at the position, else the sheet's centre. Its own sheet changes nothing. `drawing.view.copy_to_sheet {view, sheet, position_mm?, with_linked?}` makes new views, each following none of the views it was copied from (Cameron's rule). A view with views lined up with it needs `with_linked`: true copies them too, lined up with the copy; false copies it alone. A projected view copied alone becomes a base view, looking the way it did (by name when it is exactly a named view) with the settings it had as its own. The copy goes to the position, else the sheet's centre. A lined-up view's centre comes from the places alone: its place along the free axis, and its parent's centre across it. So the views that travel with a view are shifted along their free axis, and nothing needs the design's extents. `descendants` takes which views it follows, so the edit's followers and the views lined up with a view are one walk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: move and copy take a view's centre from the record (#4816) Moving or copying a view to a sheet worked out a view's centre with its own copy of the lined-up rule. It now uses `Drawing::view_centre`, and a lined-up view's place moves along its free axis by `ProjectedSide::along`, so the rule is written once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing sheets: a change of paper lists the views it leaves outside, and moves none (#3875) W18 S5, its first part. `Sheet::drawing_space` is where a sheet's drawing goes: inside its drawing border, or the whole paper with none. Nothing on a sheet is saved against the border, so a change of paper regrows the border and its zones by itself. `engine::drawing_view::outside` lists the views whose boundary (`on_sheet`) leaves their sheet's drawing space, sides included. `drawing.sheet.set` lists them after a change of paper (size or orientation; nothing else moves the space, so nothing else is measured), and moving or copying a view to a sheet lists those it puts outside there. None is moved: where a view goes stays the author's choice. A view with nothing to measure is listed by its placeholder, with why. Tests: the drawing space with the ISO border, a filing margin and no border, and margins that leave none; A3 to A4 either way up listing the right and top views with their boundaries and moving nothing; a border through a view's edge leaving it inside, 0.1 mm less paper putting it outside; a change of name checking nothing; undo; a move to a smaller sheet listed and a copy that fits not; a placeholder listed as `past_walk_limit`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing ids and the sheet border: the tests the slices left out (#3890, #4816, #3875) What the slices' planted faults found untested, now each caught by a test: - a file change keeps the session's id source: a drawing made after `file.new` draws the same ids in two sessions seeded alike; - a copied view's ids come from the session's id source, seeded alike giving the same copies; - a crash report's replay carries the session's id seed, from a caught panic (the seed when the command started) and from the journal of an unclean exit (the seed at its base); - a view short of the sheet's border by less than a rounding is still inside it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: listed by id, whatever order they were added in (#4816) The record's rule says a drawing saves its later records by id, never in the order they were added in. Views were kept, and saved, in the order they were added, so the same views added in another order saved as other bytes. - `Drawing::add_views` adds views where their ids put them. It is the one way views are added: `drawing.view.copy` uses it, and so do the test fixtures. - `Drawing::validate` checks the order, so every edit keeps it. - Reading puts a file's views in id order before it checks the record. The order means nothing, so a file in another order (saved before views went by id, or edited by hand) isn't damaged: it opens, and saves by id. - What a command lists in the drawing's order (the views a change of paper leaves outside, an edit's followers) now comes by id. No format change: no release has saved a view (the views change is still pending). Tests: - views added out of id order are listed and saved by id; - the same views added the other way round make the same drawing and the same bytes, which read back as saved; - a file that lists them the other way round reads by id, as the same drawing, and saves as those bytes; - an edit that leaves them in another order breaks the record's rule; - the existing tests now index the file by id order, and the engine's lists are by id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: the dimension record, saved by name on a view (#4896, W19b) A drawing saves its dimensions: each on one of its views, measuring named corners or edges of the view's model (a linear distance between two corners, a straight edge's length, or the angle between two edges), with the direction and reading W19a measures with, how far its line sits from what it measures, where its text sits on paper, its decimals and any text shown instead of the number. A reference names its body, the feature that made the body and its occurrence path, never a picked point. Nothing worked out is saved. - Dimensions are listed by id, read in that order whatever order a file has, as views are: one helper pair (add_by_id, in_id_order) now keeps both lists. - One cap for the names a saved model reference keeps (MAX_REFERENCE_NAME_BYTES, used where 512 was written three times), and the corner's edge cap shared with construct's vertex references. - LinearDirection and Reading save as snake_case words; a direction along a line saves as its two coordinates. - A direction along a line is at most 1e9 each way (format::MAX_REAL, the limit the file's number check holds every real number to): a whole number past it passes that check, reads as a real, and would save as one the next read refuses (found by the mutation test). - One pending drawing-format file (dimensions.rs). Tests: what each dimension saves and reads back as the same bytes, a file in another order reads by id, an older drawing has none; every rule on both sides of its cap, unknown fields at every level, the id rule, and the count cap; the plate fixture carries one dimension of each kind, so the existing round-trip and mutation tests cover them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Edges and corners by name: the matching step and the shared ends, each in one place (#4896, W19b) A dimension finds an edge by name the way a fillet does, but never takes the piece nearest an old pick, and finds a corner where its named edges meet, refusing to choose between two. Both steps are split out of the code that already does them, so there is one rule each: - BodyEdges: a body's edge and face names, worked out once for every name looked up on it, and `matching(name)`: the edge with that name, else the one edge between the faces it names, else the pieces of it. locate_edge now calls it and then takes the piece nearest the pick, as before. - construct::shared_ends: the ends every named edge has, each once. named_vertex keeps the one nearest its point, as before. No behaviour change: every caller of locate_edge, find_edge and the vertex references gets the same results. The existing naming, joint and construction tests cover both paths; three new ones pin what they left open: a vertex only where every named edge ends, the piece of a split edge nearest the pick, and a lone match through the stem of a face taken as a piece, with the warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A body's frame through an occurrence path, in one place (#4896, W19b) Document::body_frame_at(body, path): the placement the path leads to, then the body's offset, without expanding every assembly instance. It is what EmbossFace::body_frame composed for its saved path; that now calls it, and drawing dimensions use it for the occurrence path they save. No behaviour change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions found again in the model, strictly (#4896, W19b) Document::resolve_dimensions(model, drawing, ids) works out each dimension as the model has it now: - An edge by its name, or the one edge between the faces it names (BodyEdges::matching), never the piece nearest an old pick. A corner is the one end all its named edges, or their pieces, share; two shared ends are ambiguous, never "the nearer one". - The body by its name and the feature that made it, placed through the saved occurrence path (Document::body_frame_at). A same-named body made by another feature isn't it. - Status: ok; overridden (the text given is shown, the value still worked out); ambiguous (the candidates, at most 16, none chosen); suppressed (its feature is suppressed or rolled back, read from the evaluation's own skipped mark); lost (why); unmeasurable (found, but the view can't measure it so, in W19a's words, such as a line seen end-on). - The value comes from W19a on the view's basis. An angle is the smaller one between the two lines, or pi minus it on its other side, whichever way the kernel runs the edges; a split edge's pieces on one line are still that line. A split edge's length is ambiguous. - The text: the override, else length_number in the drawing's unit (or the design's) with the dimension's decimals, or degrees for an angle. - The cost: each body named is looked up once per call, and its edge names worked out once, however many dimensions name it. A body none names isn't touched. Not saved: nothing here changes the drawing. The last good value of a lost dimension needs the session, so it comes with the commands (W19d). Tests (engine, an 80 x 40 x 30 box, values by hand): front and right view values, foreshortening and text, the drawing's own unit, several at once and an unknown id; a change and its undo; a chamfer's 45 and 135 degrees, and end-on in the front view; lost after a fillet and back on undo, suppressed and back, a path to no occurrence, a new box with the same body name; a notched edge's length ambiguous with both pieces while its end corners still read 80 and its angle to the left edge 90 degrees, neither exact; an angle on a piece of an edge whose slot is then suppressed still reads 90 degrees, found through the stem of the face it was cut from, so not exact; one edge named twice gives two corner candidates; an override shown while the value follows the model. Not covered by a test here: a part placed twice through real occurrences (only a path to no occurrence is); the once-per-body cost (not measured). A drawing unit outside display_units' five (um, yd, km, mil, nm, inch, µm) shows millimetre numbers, the formatter's existing fallback; named as its own follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing sheets: a sheet with anything on it goes only with it, when asked (#3875) drawing.sheet.delete refuses a sheet with anything on it, and its error names what is there: its views and the dimensions in them, the first few ids of each. With `contents: "delete"` the sheet goes with everything on it as one undo step, which an undo brings back whole. What is on a sheet is said once, in the record (Drawing::on_sheet), and Drawing::remove_sheet takes the sheet out with exactly that; each later kind of record on a sheet (a sheet's own sketches) adds itself there, so delete covers it with no change here. drawing.sheet.set's list of a sheet's views uses the same function. Nothing on another sheet goes with it: a view there projected from one of the sheet's views would lose its parent, so the delete is refused naming those views, to be moved or deleted first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: a view found by id by halves, in one place (#4816) The views are listed by id, so Drawing::view and Drawing::view_index find one by halves instead of walking the list. Every lookup by id uses them: a view's chain of parents (so resolving a view no longer walks the list once for each parent), the views' rules (which no longer build a map of every view to check each chain), a dimension's view, and the engine's view commands and outside check. The rules check the id order before anything looks a view up, so a file with its views out of order is still refused as before. Measured on a drawing of 4096 views (1024 chains of a base view and three views projected one from another), median of 30 runs: 4096 lookups by id took 6.85 ms by a linear find and take 105 us by halves; the rules' check took 1.77 ms and takes 1.45 ms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: a damaged chain names the view whose parent is missing (#4816) A view whose chain of parents breaks further up was refused with "its parent X isn't one of the drawing's views", naming a view that isn't its parent. Further up the chain, the refusal now names the view whose parent is missing: "its chain of parents reaches view Y, whose parent X isn't one of the drawing's views". A missing parent of the view itself reads as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: add one to a view, and read it back (#4896, W19d) drawing.dimension.add puts a dimension on a saved view of the open drawing; drawing.dimension.inspect reads one back with what it measures now. Adding is one undo step of the drawing; neither evaluates the model. What a dimension measures is picked and saved by name, never by the point picked. Document::edge_reference and corner_reference turn a pick into the record's names: an edge is a point on it in the design's frame or {body, index} (its index among the body's edges as the model lists them, as an ambiguous dimension's candidates are given), and a corner is a point or the edges that meet there, each with the occurrence path its body is placed through. A pick that doesn't name one thing is refused, saying what it found: no edge, a point on several edges, a corner of more than one body, edges that don't meet. Each body's edges are placed in the design's frame once per pick, and only the body picked has its edge names worked out. The kind is given as the record saves it ({linear: {a, b, direction?, reading?}}, {length: …}, {angle: …}), read by the record's own types, so a command and a file read the same words. A new dimension's line is 10 mm from what it measures (until a dimension style gives it), shows 2 decimals, and its text is centred on its line; the text's default place at its centred point (Q58) comes with the layout on paper. The inspect reply has the record, the status (ok, overridden, ambiguous, suppressed, lost or unmeasurable, with why), the value (model mm, or degrees for an angle), whether it is foreshortened, the text, whether every name was found by exact history, and an ambiguous dimension's candidates. One rule, one place: the corner rule's point tolerance is construct::point_tolerance, shared by the picks; a dimension by id is Drawing::dimension, which the resolver uses too. Not here yet: edit, the view's list of its dimensions, the layout on paper and the last good value (the next slices); delete waits with view delete for the drawing UI's choice of one delete command or one for each kind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: change a dimension's settings (#4896, W19d) drawing.dimension.edit {dimension, set} changes what a dimension shows and where, as drawing.view.edit changes a view: the offset of its line, its text's place, its decimals, the text shown instead of its number (null shows the number again), and its kind's direction, reading or other side. The reply names the fields that changed. The same values again change nothing and add no undo step (the drawing is put back only when it differs). What a dimension measures isn't a setting: to measure something else, add a new one. A setting its kind hasn't got is refused (an angle has no direction, only an angle has an other side); every changed record passes the check a file read runs, so an edit can't save what opening the file would refuse. A dimension's place in the list is Drawing::dimension_index, which Drawing::dimension now uses too, as a view's is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: inspect lists a view's dimensions (#4896, W19d) drawing.view.inspect answers `dimensions`: the ids of the view's dimensions, in id order, so a caller can find them and read each with drawing.dimension.inspect. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing views: a copied view takes copies of its dimensions (#4896, W19d) drawing.view.copy_to_sheet copies each copied view's dimensions with it, each with an id of its own on the copy, in the same undo step: they are part of what the view shows (the W19d plan's question 1, its recommended answer). A dimension's text placed `at` a point is from its view's centre, so it keeps its place on the copy. A moved view keeps its own dimensions as they are: they name the view by id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: the last good value, in memory (#4896, W19d) A dimension that can't be measured now (its edge filleted away, its corner gone) gives `last_good`: the value it last read this session, as `value` gives it (model mm, or degrees for an angle). Each value a dimension reads is kept by drawing and dimension in the session, and a scratch copy keeps the session's. It is never saved (the W19b plan's question 3), so a design's drawings don't change with every model edit, and after reopening a lost dimension shows no number. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: hostile values anywhere in the commands' params (#4896, W19d) A hostile value in each place of drawing.dimension.add, .edit and .inspect's parameters, at every depth (the kind, its picks and paths, a corner's edges, the edit's settings), with 1 000-item lists, the largest index and 600-byte text besides the engine's shared hostile values: no panic, and the drawing keeps its rules. The engine's own hostile test sends only its list of key names, none of which these commands read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: a new dimension's defaults, tested (#4896, W19d) The offset (10 mm), the text's place (centred) and the decimals (2) a dimension is added with when not told. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * A plane carried by a transform, in one place `assembly::map_plane` is public and gives `None` for a transform that flattens the plane; its two copies (the occurrence mapping, which keeps the plane as it was, and the face anchor's, which refuses) now call it and keep their own fallback. W20a's radial dimensions use it to carry a view's plane into a body's frame. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * Drawing dimensions: a circle's or an arc's radius and diameter (#4896, W20a) A radial dimension saves its edge by name and whether it shows the diameter. It reads the edge's exact circle in the view's plane through the edge (`Body::planar_edges`, as the view draws it), so a view that doesn't see the circle face on can't measure it; its text is R or Ø before the number. `drawing.dimension.add` takes `{radial: {edge, diameter?}}`, a diameter by default for a whole circle or half a turn or more (a hole's rim is two halves), and refuses a dimension of any kind its view can't measure; `drawing.dimension.edit` switches `diameter`, keeping the id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
d11d268597 |
Render: reject excessive pixel work during preflight (#5088) (#5728)
* claim: #5088 * fix: reject excessive render pixel work during preflight (#5088) |
||
|
|
313d2bdb41 |
Sketch: snap where two recent centre and midpoint guides meet (#8694) (#8744)
* Sketch: snap where two recent centre and midpoint guides meet (#8694) The alignment guide remembered one reference and did not move the cursor. Keep the previous centre or midpoint as well, and snap a drawing point to where their guides cross. * Sketch: keep the recent-guide crossing snap off until asked (#8694) The crossing snap was on whenever alignment guides were. Give it its own Sketch preference, off by default, so the guides stay visual until someone turns the snap on. * Sketch recent snap: ar/he/vi strings and clippy borrow fixes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: bherbruck <brennen.herbruck@gmail.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
4a781b8cd8 |
MCP: --param-check checks calls to the running app (#9072) (#9073)
* claim: #9072 * MCP: --param-check checks calls to the running app (#9072) `mcp --connect` (and `mcp` once it bridges to an app) took `--param-check` and forwarded every call unchecked, while the app's own session is always off. `Remote` now checks each `engine.execute` and `engine.batch` against this server's command contracts before sending: `strict` refuses a call, or a batch with such a step, without sending anything, and `warn` adds `param_warnings` to the app's reply (each step's result in a batch). A command with no contract here goes to the app unchecked. `Auto` carries the headless session's level over when it bridges to a late app. `batch_preflight::contracts` is the contract half of the existing preflight, for a runner in another process. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> |
||
|
|
f6393ef211 | Regenerate generated files and fold pending files [skip ci] |