Skip to content

fix(desktop): the assistant sees the print check of its changes, blocks measure before they cut - #357

Open
ManuelStaggl wants to merge 12 commits into
fix/print-check-auditfrom
fix/assistant-check-rules
Open

ManuelStaggl wants to merge 12 commits into
fix/print-check-auditfrom
fix/assistant-check-rules

Conversation

@ManuelStaggl

@ManuelStaggl ManuelStaggl commented Nov 12, 2026 •

Copy link
Copy Markdown
Collaborator

A-findings of the full audit (2026-11-12) on the rules for the assistant round the print check, on top of #353, plus the report of 2026-11-12 that the assistant does not optimise print time and material on its own, and the points of the independent review of this PR. origin/main is merged in (undo pattern of #348, block refusals of #355).

R1 – the check reaches the assistant in the turn that changed the design

Before: "Read them after your last change, before you call the part done" was a request. The check reached the model with the next user message; a turn could end on status block it never saw.

After: the work log (workLog.ts) reads SolidCraft's history before and after every changing call. When the design changed, it hands the tool's answer to withCheckOfChange (printCheckNote.ts), which waits until the mirror has delivered that revision and the renderer has reported its check, and puts the note behind the answer. It waits outside the work log's queue.

Why this and not a step at the end of the turn: Claude Code (gate proxy), ChatGPT and local models (loop.ts) all call tools through the one chain track(upstream) in index.ts. One hook there covers all three; a turn-end step would have needed three implementations and a guard against loops. A tool answer cannot loop: no change, no note.

Details, after the review:

  • Two stages. With two bodies or a joint the renderer sends the mesh check at once (measuring: true) and the full check when SolidCraft has measured clearance and joints. The answer waits up to 1.5 s for the full check, then takes the mesh check marked as preliminary; after 3 s without any check it says so. Timings at SolidCraft 0.40.1 without a window (median of 5): mirror and mesh check 211 ms for a box with its lid, 141 ms for a hinge; the hinge's two joint sweeps 498 ms; inspect.clearance 1 ms for both; the renderer adds 250 ms of quiet time. Not measured: the renderer's own check time in the app.
  • What counts as a change: the undo depth moved, or a change replaced what Redo held (designChanged). The revision alone does not: trials, saves and a block that was taken back move it. Measured: a probe batch that is rolled back leaves revision, undo and redo as they were; body.print_face moves revision and undo depth, so it gets its check.
  • Told once. CheckTold gives the full note the first time in a turn and whenever status, findings or the preliminary mark change; else one line plus the measures that changed. A new user message starts over.
  • Changing tools now include partbuddy_place_drawing and partbuddy_move_drawing (check, queue, lock while a question is open, a line in the work log). The other PartBuddy tools (view, standard, ask, plan, assembly, design_rules) change no geometry.
  • When the history cannot be read, the answer carries no check and the log says so.

Texts to the assistant:

  • VIEW_GUIDANCE, replacing the request: PartBuddy checks the part for printing after every change and shows the result to the user as a light; the findings come behind the answer of each tool call that changed the design, and partbuddy_view gives the latest ones.
  • Pending behind a tool answer: [PartBuddy: the ready-to-print check of the latest change is still running; call partbuddy_view for it before you call the part done.]
  • Pending from partbuddy_view itself: [PartBuddy: the ready-to-print check of the latest change is still running; it will be here in a moment.]
  • Preliminary: Preliminary: clearance and joints still being measured (bodies that overlap or come closer than a push fit, joints that stop before a limit); call partbuddy_view for the full check before you call the part done. A clean preliminary check reads nothing found so far (build volume, wall and floor thickness, overhang angle, area on the bed, open bodies, failed features, bodies that touch).
  • Unchanged: [PartBuddy: ready-to-print check after this change: unchanged, still not ready to print, the same 1 finding as before.] / … unchanged, nothing found.], followed by Measures that changed: and those lines when there are any.

R3 – a clean check says what it looked at

Before: ready to print, nothing found. / The print check in partbuddy_view still judges what you build.

After (from the finding kinds of printCheck.ts, withClearance, withMotion):

  • clean: … nothing found in what it checks (build volume, wall and floor thickness, overhang angle, area on the bed, open bodies, failed features, bodies that touch, bodies that overlap or come closer than a push fit, joints that stop before a limit). and at the end Not checked: strength and layer direction under load, the fits and hole allowances you chose, details and text too fine to print, bed-edge chamfers. Judge those yourself.
  • with findings the last line ends with the same Not checked: … sentence.
  • standard topics: None of this has been printed by PartBuddy yet, and the print check does not test these rules: measure what you built against them.

R54 – blocks measure the plastic they need

One measure for all four: a probe cylinder is laid in as a body in a batch that is rolled back, and inspect.interference says how much of it lies in the part. The cylinder starts under the face (behind a keyhole's lip, at half the depth of a pocket, seat or countersink), so a curved or sloping face is not read as a gap.

Block Needs Measured
keyhole entry wide, lip + pocket + 1.2 mm deep, behind the entry and behind the screw's rest before anything is cut
magnet pocket + 1.2 mm round it + 0.6 mm under it before anything is cut
insertSeat the maker's wall round it (at least 2 mm, as a boss) + 0.6 mm under it after the cut, along the way the hole went in (read from what it took out of the body: solid.hole names no direction); taken back when short
woodScrew the screw's diameter round the countersink + 0.6 mm of clearance hole under it the same
any cutting block the body stays one body after the cut; taken back

0.6 mm under a seat and under a countersink are our own choice (three layers), marked so in the code.

Refusals, as the engine gave them:

  • Not built: at [220, 30, 0] P3 has 3 mm of plastic where 6.7 mm are needed (the mean over a width of 9 mm; the far side, an edge or another cut takes the rest); a keyhole needs the part 6.7 mm thick behind its entry and its slot. Thicken the part there, move the keyhole away from edges and other cuts, or use woodScrew. (an entry over the edge names the entry: at [30, -3, 0] … 2.51 mm)
  • magnet: … has 3 mm of plastic where 3.3 mm are needed (the mean over a width of 8.7 mm; …); a pocket needs 1.2 mm of plastic round it and 0.6 mm under it. Thicken the part there, move the pocket away from edges and other cuts, or take a thinner magnet.
  • insertSeat: … has 6.54 mm of plastic where 7.3 mm are needed (the mean over a width of 8 mm; …); the seat of an M3 insert needs 2 mm of plastic round it and 0.6 mm under it. Thicken the part there, move the seat away from edges and other cuts, or use a boss. With nothing under the probe's start: has less than 3.35 mm of plastic.
  • woodScrew: …; a countersunk hole for a 4 mm wood screw needs 4 mm of plastic round the countersink and a plate at least 2.65 mm thick. Thicken the plate, or move the hole away from edges and other cuts.
  • split: Not built: it would leave 2 bodies where Strip was one (Strip (1)): the cut goes right through the part there. Give a point with more plastic round it, or a smaller size

Success answers end with PartBuddy measured that plastic before building: it is there. (keyhole, magnet), PartBuddy measured that plastic: it is there. (insertSeat, woodScrew), or PartBuddy could not measure that plastic this time: check it yourself. insertSeat adds Each seat needs 2 mm of plastic round it and 0.6 mm under it.

At the engine (SolidCraft 0.40.1, audit harness):

  • The shared volume is exact, not facetted: seven probe cylinders wholly inside a block, a rod and a ball, along the axes and askew, each gave 1.0000000 of their volume; with 0.02 mm of 2.8 mm outside 0.99286; with 0.01 mm of the width over an edge 0.99993. The threshold stays at 0.999.
  • Built, with the measured sentence: keyhole in 8 mm, magnet in 4 mm, insert M3 in 10 mm, wood screw in 8 mm; magnet and two insert seats on a 45° face; magnet and insert seat on a rod of 20 mm radius. On the rod the seat took 0.02 mm more of the stock than on a flat face; 0.3 mm of the seat's depth are allowed for that.
  • Refused, nothing left behind (volumes and undo depth unchanged): keyhole in 3 mm and with its entry over the edge; magnet with 0.3 mm under it and 1 mm from a wall; insert seat in 3 mm, in a plate exactly as thick as the seat is deep, 2.5 mm from an edge, and on a 3 mm pilot hole through the part; wood screw 5 in a 3 mm plate and 5 mm from an edge.
  • A probe batch leaves revision, undo and redo untouched.
  • The harness's bloecke cases (40) pass.

Not measured: the same conditions in mechanismTool.ts (others work there), and whether two pockets of one call leave enough plastic between each other (each is measured against the body before the cuts).

Material and print time (reported 2026-11-12)

Rule group economy (fifth group, on by default; settings switch "Design lean" / "Sparsam konstruieren", partbuddy_design_rules { economy }). Text for a 0.4 mm nozzle and 0.2 mm layers:

  • Material and print time, from the first shape on and not as a clean-up: build a shell, not a block. Slicers print 0.9, 1.35, 1.8 mm (two to four lines) as wall and fill anything thicker with sparse infill, so stiffen with ribs as thick as the wall, at least two lines (0.9 mm), instead of thickening the wall. Keep the part low as it prints: every 0.2 mm of height is one more layer, and print time grows with the layers. Shape it so it needs no support, which costs more time and material than any other choice. Leave out whole regions that do nothing (an open frame instead of a plate, a shorter body); small pockets and holes in a filled region add walls and save little or nothing. Split a part only when that saves support or much height and a joint is acceptable. Loaded regions, sizes the user names and what the user wants solid stay as they are. Infill, speed and layer height are set in the slicer: never promise grams or minutes. The print check lists each body's solid volume, height, layers and support area: compare them before and after a change.

The report line becomes After each design step, name what you added on your own, what you did for material and print time and the sizes you snapped in one short line, e.g. "…, shell 1.35 mm with two ribs instead of a solid block".

Measures with every check (tool answers, next message, partbuddy_view), computed in the renderer from the mirrored mesh with meshMetrics and overhangOf:

Per body as it prints. Solid volume overstates what thinning a thick region saves, because the slicer fills it sparsely anyway; the shell estimate (two wall lines on every surface, 15 % infill inside: an assumption, not the slicer's settings) is nearer to the filament. What counts for sure: fewer layers, less area to support, less surface. Promise neither grams nor minutes:
- "Box": 6 cm³ solid, about 2.58 cm³ as a shell, 10 mm high (50 layers), 2200 mm² of surface, 600 mm² on the bed, 0 mm² leaning past the overhang limit.
- "Pin": 90 mm³ solid, about 90 mm³ as a shell, 10 mm high (50 layers), …
- and 3 more bodies.

The shell estimate: surface × two lines, never more than the solid, plus 15 % of the rest. A block 40 × 40 × 20 mm reads 32 cm³ solid and about 9.7 cm³ as a shell; a wall of four lines reads the same in both. Six bodies are listed, the rest counted; volumes under 1 cm³ are given in mm³. No mass: the material profiles carry no density. A body that cannot be measured is left out and named in the log; a report the main process refuses is sent again without measures. packages/geometry is unchanged; laid (print face down) and smallOverhangReach are not exported there, so bodyMeasures.ts repeats the pose and the reach (marked shortcut:).

Yardstick

Topic Rule with number Source 1 Source 2 Contradiction
Walls as lines Whole multiples of the line width; 0.4 nozzle: 0.45/0.9/1.35/1.8 mm for 1–4 perimeters; strength mostly from perimeters; shells typically 0.8 mm Prusa, https://help.prusa3d.com/en/article/modeling-with-3d-printing-in-mind_164135/ , https://help.prusa3d.com/en/article/layers-and-perimeters_1748 Protolabs Network, https://www.hubs.com/knowledge-base/selecting-optimal-shell-and-infill-parameters-fdm-3d-printing/ Minimum wall differs (Hubs 0.8 mm, UltiMaker 0.5 mm, Stratasys Direct 4 × layer). No source names a thickness above which more wall stops paying off, so the rule names none.
Ribs instead of thick walls Ribs stiffen thin walls and large flat areas; in FDM a rib needs no thinning. Rule: as thick as the wall, at least two lines Stratasys Direct FDM guide (p. 4, 7), https://www.stratasys.com/siteassets/sdm/content---website-storage/design-guides/dg_sdm_fdm_0725a.pdf Protolabs Network, https://www.hubs.com/knowledge-base/dfm-tips-for-3d-printed-parts-with-thin-walls/ Yes: Hubs gives 50–75 % of the wall, the rule of injection moulding against sink marks; on a 0.9 mm wall that is one line, under the thinnest wall that prints. Not taken over. Stratasys Direct also says solid can build faster than hollow with ribs.
Height in Z Print time follows the layer count: a 10 × 30 mm cylinder is about 100 layers lying, 300 standing (0.1 mm) Protolabs Network, https://www.hubs.com/knowledge-base/how-does-part-orientation-affect-3d-print/ Xometry, https://www.xometry.com/resources/3d-printing/5-easy-ways-to-reduce-3d-printing-costs/ (wording not verified) Partly: Markforged names wall length, not height, as the classic driver.
Support Self-supporting to 45°; bridges 5 mm (Hubs) or 10 mm (UltiMaker); support adds more time than any other factor Protolabs Network, https://www.hubs.com/knowledge-base/supports-3d-printing-technology-overview/ Markforged, https://www.markforged.com/blog/design-for-3d-printing-part-3-decreasing-print-time ; UltiMaker, https://ultimaker.com/learn/design-for-fff-3d-printing-maximize-your-success/ Bridge 5 vs 10 mm; overhang 45° vs 45–60° (Prusa).
Pockets and holes Holes may save material but not time: a ring took 10 % longer than the full disc; solid can build faster than hollowed Markforged, same article Stratasys Direct, same PDF (p. 7) Yes, by process: Protolabs gives −36 % volume for hollowing, for SLS/MJF/metal, not FDM.
Bed contact Largest face on the bed; more contact, less warping Markforged, https://markforged.com/learn/composites-3d-printing-design-tips UltiMaker, as above None; Hubs: strength in XY is 4–5 × that in Z, so load can outrank it.
Splitting Fewer supports, fits the bed, shorter build; costs a seam, pins and fit allowance Prusa, as above Markforged, Stratasys Direct, as above Clearance figures differ and are printer-specific.
Large flat faces Prone to warping; break up with ribs, round corners Protolabs Network, https://www.hubs.com/knowledge-base/key-design-considerations-3d-printing/ Stratasys Direct, same PDF (p. 4) Severity depends on the machine. Pillowing of large tops: one source only (Polymaker), not in the rule.
The slicer's part Infill 10–20 % usual, above 40 % rarely sensible; layer height is the main factor of print time (0.1 → 0.2 mm halves it) Prusa, https://blog.prusa3d.com/everything-you-need-to-know-about-infills_43579/ Protolabs Network (infill article above); Markforged, as above Hubs contradicts itself on infill vs shells for strength; Prusa sides with shells.

Test expectations changed

  • test/printCheckNote.test.ts: ready to print, nothing found → the new clean text and Not checked: ….
  • test/standardTool.test.ts: closing sentence of a standard topic.
  • test/blockTool.test.ts: the keyhole sends a trial batch (solid.cylinder inspect.interference … sketch.inspect) before its cuts; MAGNET has around and under; the R54 cases run on a plate that answers probes and holes as the engine does.
  • test/designRules.test.ts: rule list and defaults carry economy; the report line names material and print time; ribs.
  • e2e/app.spec.ts: the list of rule switches carries economy.
  • e2e/solidcraft.contract.spec.ts (wall plate): the answers must carry PartBuddy measured that plastic before building: it is there. after the keyholes and the magnets and PartBuddy measured that plastic: it is there. after the wood screws.
  • New: test/checkOfChange.test.ts (both routes, no change, bounded wait, two stages, moved revision without change, a failing call that changed the design, undo, two calls at once, unreadable history, drawing tools, told once), test/bodyMeasures.test.ts.

Checked

pnpm run typecheck, eslint . and vitest run in apps/desktop. Audit harness against SolidCraft 0.40.1: bloecke (40 cases), the block cases on flat, sloping and curved faces, the probe shares and the timings above. Not run locally: the app, Playwright E2E and contract tests (CI runs them), a real chat.

🤖 Generated with Claude Code

ManuelStaggl and others added 8 commits November 12, 2026 14:13
Audit findings W1, N-bloecke-2 (A) and the "other bodies" part of W2/T4
and N-bloecke-1 (A).

Measured before on real SolidCraft: `screwHole body: "A"` with a point
on B cut B (−181.6 mm³) and answered "Built screwHole on A"; a body
named "Ghost" went through the same way; on an edge two bodies share,
both were cut; a slot without depth cut every body behind it.

- Every step of a block names its body (`targets` / `body`).
- The tool refuses a body that does not exist and lists the bodies there
are.
- After building it compares the volumes of all bodies: a block that
changed another body, or nothing on its own, is undone step by step and
refused with the reason. A block that fails half way leaves nothing
behind.
- `docs/engineering-notes.md`: why the way back is `edit.undo` (a
`rollback` batch and `edit.restore_checkpoint` both fail on SolidCraft
0.40.1).

Measured after, SolidCraft 0.40.1, the audit's cases `bloecke-w1` /
`bloecke-w2` (39 cases pass): all 14 wrong calls are refused with 0 mm³
changed on either body; on the shared edge only A is cut (−90.792 mm³);
the bodies behind a through slot stay whole.

Still open (own PR): a through hole or slot in a hollow body still goes
through both of its own walls.

Checked: typecheck, eslint, 713 unit tests.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com>
Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
…ll that made it

Audit R1: reading the check after the last change was only asked of the
assistant, so a turn could end on a blocked part it never saw checked. The
work log now hands every answer of a call that moved the design's revision
to the check: it waits up to 5 s for the mirror and the check of that
revision and puts the findings behind the answer, or says that the check is
still running. All three routes share this tool chain.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… did not

Audit R3: "ready to print, nothing found" and "the print check still judges
what you build" promised more than the check covers. The note now lists what
was checked and what was not (strength and layer direction, chosen fits and
hole allowances, fine details, bed-edge chamfers), and the standard topics
say that the check does not test their rules.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…ds and odd insert seats (#355)

Audit findings W3, S-02/T8 and N-bloecke-4 (A), measured on real
SolidCraft before:

- `text` without `text` built the form's placeholder word "PartBuddy"
and answered "Built text …".
- `tappedHole` built modelled threads from M2 (core 1.6 mm) without a
warning, while `kind: screw` refuses a printed thread under M5
("strips").
- `insertSeat diameter: 500, depth: 500` drilled the whole carrier away
(143 623 mm³) and answered "Built"; `0.01` built a hole of no volume.

Now, in the assistant's block tool:
- text needs its words;
- a tapped hole under M5 is refused with a pointer to `insertSeat` (one
rule, `TAPPED_FROM`, for block and screw joint);
- an insert seat stays within a tenth of the maker's hole and between
one and three insert lengths deep; a boss keeps the maker's wall.

Checked: typecheck, eslint, the block tests (one new test holds the
measured cases).

Not in this PR: the form of the user (BlockPanel) still offers tapped
holes under M5 (finding T8, B).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com>
Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
…the plan's frontier model (#354)

PartBuddy passed no `--model`, so a new chat ran Claude Code's own
default – the frontier model of the user's plan – and the menu showed
that entry as "Default (recommended)", as if PartBuddy recommended it.

- Without a kept choice a chat starts with `opus` at `medium` effort
(`CLAUDE_STANDARD`); a plan without it falls back to its first model.
- Claude Code's "Default (recommended)" entry is left out of the model
list. A kept choice of it falls back to the standard.
- Provisional: the models are compared on PartBuddy's own tasks in phase
7 of the audit of 2026-11-12.

Checked: typecheck, eslint, 715 unit tests. The E2E expectations for the
chip text are adapted but were not run here.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: ManuelStaggl <17688572+ManuelStaggl@users.noreply.github.com>
Co-authored-by: Claude Fable 5.2 <noreply@anthropic.com>
…s cuts need

Audit R54: the blocks named what the surrounding plastic must be only in
their answer and built without measuring. A keyhole and a magnet pocket are
now measured on trial before anything is cut (the pocket with what must stay
round and under it, laid in as a probe body), the seat of an insert is
checked by the volume it takes, and a block that leaves the body in pieces
is taken back, each with the measured value. What is still not measured
(wood screw, the wall round an insert) says so in the answer.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
…sees what a change saved

Reported 2026-11-12: the user had to ask for print time and material to be
optimised every time. A fifth design rule group (on by default, switched off
in the settings or with partbuddy_design_rules) tells the assistant what the
shape decides of both, with the wall and layer sizes of the printer in use,
and what stays the slicer's. The print check now carries per body the solid
volume, the height and layers as it prints, the area on the bed and the area
that leans past the overhang limit, so the numbers come with every check.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
@ManuelStaggl ManuelStaggl changed the title fix(desktop): the assistant sees the print check of its own changes, and blocks measure before they cut fix(desktop): the assistant sees the print check of its changes, blocks measure before they cut Nov 12, 2026
ManuelStaggl and others added 4 commits November 12, 2026 15:16
…ules

# Conflicts:
#	apps/desktop/src/main/agents/blockTool.ts
#	apps/desktop/test/blockTool.test.ts
…when the design changed

Review of PR 357. With two bodies or a joint the mesh check now goes ahead
of the clearance and joint measures and is told as preliminary, so no step
waits for SolidCraft's measuring (waits set from timings at the engine). A
call counts as a change by the undo history, not by the revision that trials
and saves move too. The same verdict is told in full once per turn and then
in one line. The drawing tools are changing tools. A body that cannot be
measured no longer holds the report back, and the measures say what solid
volume overstates and carry a shell estimate.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Review of PR 357: half to three quarters of the wall is the rule of injection
moulding against sink marks; on a 0.9 mm wall it gave a rib of one line,
under the thinnest wall the print check accepts.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
… curved faces pass

Review of PR 357. The plastic a block needs is measured from under the face
on, so a curved or sloping face is not read as a gap. The seat of an insert
and a countersunk hole are given no direction: it is read from what the cut
took out of the body, and the wall round and the plastic under them are
measured after the cut, which is taken back when they are missing (a plate
just as thick as the seat is deep, a pilot hole through the part). The
threshold stands on shares measured at SolidCraft 0.40.1.

Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant