prechips 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. {prechips-0.1.2 → prechips-0.2.0}/PKG-INFO +77 -13
  2. {prechips-0.1.2 → prechips-0.2.0}/README.md +76 -12
  3. {prechips-0.1.2 → prechips-0.2.0}/pyproject.toml +3 -1
  4. {prechips-0.1.2 → prechips-0.2.0}/pyproject.toml.orig +3 -1
  5. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/__init__.py +1 -1
  6. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/cli.py +19 -6
  7. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/findings.py +5 -2
  8. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/inputs.py +175 -20
  9. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/joint_features.py +68 -28
  10. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/__init__.py +321 -16
  11. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/freecad_job.py +1158 -58
  12. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/render_diagram.py +32 -1
  13. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/render_inputs.py +8 -0
  14. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/measurements.py +78 -0
  15. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/model.py +545 -37
  16. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/process_features.py +106 -15
  17. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/__init__.py +10 -2
  18. prechips-0.2.0/src/prechips/rules/_bench.py +73 -0
  19. prechips-0.2.0/src/prechips/rules/_cutting_force.py +226 -0
  20. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/accessibility.py +5 -0
  21. prechips-0.2.0/src/prechips/rules/blank_credit.py +384 -0
  22. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/centre_support.py +3 -3
  23. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/consistency.py +31 -14
  24. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/coordinates.py +94 -21
  25. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/coverage.py +134 -7
  26. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/finish_route.py +2 -2
  27. prechips-0.2.0/src/prechips/rules/guided_sweep.py +1287 -0
  28. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/headroom.py +28 -9
  29. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/hold_fields.py +2 -2
  30. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/inspection.py +228 -36
  31. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/joints.py +8 -4
  32. prechips-0.2.0/src/prechips/rules/keep_piece.py +92 -0
  33. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/level_entry.py +4 -1
  34. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/manual_arc.py +174 -8
  35. prechips-0.2.0/src/prechips/rules/parting_face.py +44 -0
  36. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/prepared_blank.py +11 -8
  37. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/resolution.py +120 -17
  38. prechips-0.2.0/src/prechips/rules/saw_cut.py +166 -0
  39. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/speeds_feeds.py +39 -2
  40. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/thin_wall_under_clamp.py +4 -2
  41. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/tip_endpoints.py +3 -2
  42. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/tool_resolves.py +40 -16
  43. prechips-0.2.0/src/prechips/rules/tooling_hole.py +331 -0
  44. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/travel.py +25 -4
  45. prechips-0.2.0/src/prechips/rules/zero_recipe/__init__.py +1 -0
  46. prechips-0.2.0/src/prechips/rules/zero_recipe/faces.py +305 -0
  47. prechips-0.2.0/src/prechips/rules/zero_recipe/native.py +1237 -0
  48. prechips-0.2.0/src/prechips/rules/zero_recipe/rule.py +517 -0
  49. prechips-0.2.0/src/prechips/rules/zero_recipe/tool_setting.py +719 -0
  50. prechips-0.2.0/src/prechips/rules/zero_recipe/touch.py +341 -0
  51. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/sheet.py +471 -161
  52. prechips-0.2.0/src/prechips/sheet_sweep.py +327 -0
  53. prechips-0.1.2/src/prechips/rules/_bench.py +0 -52
  54. prechips-0.1.2/src/prechips/rules/saw_cut.py +0 -111
  55. prechips-0.1.2/src/prechips/rules/zero_recipe.py +0 -2956
  56. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/clamp_labels.py +0 -0
  57. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/boolean_pool.py +0 -0
  58. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/chain_boundary.py +0 -0
  59. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/render_png.py +0 -0
  60. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/kernel/step_faces.py +0 -0
  61. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/report.py +0 -0
  62. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/_envelope.py +0 -0
  63. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/_profile.py +0 -0
  64. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/construction.py +0 -0
  65. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/datum_consistency.py +0 -0
  66. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/engagement.py +0 -0
  67. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/envelope.py +0 -0
  68. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/finish_coverage.py +0 -0
  69. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/fixture_interference.py +0 -0
  70. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/geometry_common.py +0 -0
  71. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/indexing.py +0 -0
  72. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/internal_corner_radius.py +0 -0
  73. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/op_chain.py +0 -0
  74. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/op_order.py +0 -0
  75. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/purchased_tooling.py +0 -0
  76. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/reach.py +0 -0
  77. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/sizing.py +0 -0
  78. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/stickout.py +0 -0
  79. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/stock_diameter.py +0 -0
  80. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/turned_profile.py +0 -0
  81. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/turning_deflection.py +0 -0
  82. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/rules/vise.py +0 -0
  83. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/telemetry.py +0 -0
  84. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/tokens.css +0 -0
  85. {prechips-0.1.2 → prechips-0.2.0}/src/prechips/trig.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: prechips
3
- Version: 0.1.2
3
+ Version: 0.2.0
4
4
  Summary: Checks before chips: deterministic validation of machining process plans against part geometry and a shop inventory.
5
5
  Author: Pedro Paulo Vezza Campos
6
6
  Author-email: Pedro Paulo Vezza Campos <pedro@vezza.com.br>
@@ -65,8 +65,9 @@ shows ample real land: at least 2.4596 mm (about 2.46 mm), versus the 0.5 mm
65
65
  policy. Missing circular-topology proof, not physical land, keeps S8/S9's
66
66
  post-plunge retouches unknown pending [#208](https://github.com/pedropaulovc/prechips/issues/208).
67
67
  Release 0.1.1 includes #198, #192 and #199 with that documented interim exit 4.
68
- Release 0.1.2 adds #235, #236 and surface-profile zones and keeps that exit 4:
69
- exit-0 restoration by #208 is deferred to a later release;
68
+ Release 0.1.2 adds #235, #236 and surface-profile zones and keeps that exit 4.
69
+ Release 0.2.0 breaks input schemas without compatibility shims and still keeps
70
+ that exit 4: exit-0 restoration by #208 is deferred to a later release;
70
71
  the authored plan and its requirements are unchanged.
71
72
 
72
73
  See the example guide's [print and native acceptance limits](examples/README.md#print-and-native-acceptance-limits).
@@ -83,6 +84,10 @@ uv run ruff check .
83
84
  uv run ruff format --check .
84
85
  ```
85
86
 
87
+ While iterating, `uv run scripts/affected_tests.py --base main` runs only the
88
+ tests a change can affect (`--dry-run` prints the selection); CI always runs the
89
+ full suite.
90
+
86
91
  Install [FreeCAD 1.x](https://github.com/FreeCAD/FreeCAD/releases) for real
87
92
  geometry checks. Discovery uses `FREECAD_CMD` first, the installed Windows
88
93
  FreeCAD 1.1 path next, then `FreeCADCmd`/`freecadcmd` on `PATH`. For example,
@@ -100,9 +105,9 @@ two pytest workers and load-scope scheduling, caps extra native workers at one,
100
105
  records durations (complete in JUnit) and uploads the pytest log and JUnit
101
106
  results even on failure. Successful geometry results persist across runs in a
102
107
  cache namespaced by runner OS/architecture, pinned AppImage SHA-256, cache epoch
103
- and engine source digest. Each immutable save key also includes the run ID and
104
- attempt; restore uses the matching namespace prefix. Only successful test jobs
105
- publish the per-run cache.
108
+ and engine source digest (only the sources `freecadcmd` executes). Each immutable save key also includes the shard index,
109
+ run ID and attempt; a shard restores its own latest entry, else the shared
110
+ namespace prefix. Only successful test shards publish their per-run cache.
106
111
  The product reports unknown geometry (`?`, exit 4) without FreeCAD;
107
112
  skips do not change that behavior.
108
113
  CI also sets `PRECHIPS_REQUIRE_PRINT_BROWSER=1`: a missing or unlaunchable
@@ -267,16 +272,31 @@ only when it is read across a face: a drawn band whose feature names the face
267
272
  extent in `tilt_across` ([features](docs/features.md#angle-bands-read-across-a-face)),
268
273
  or a BASIC angle a surface profile bounds by `tilt_across`. Inspection judges it as
269
274
  the linear zone it reads across that length, on gauge resolution alone; any other
270
- angle band is `?` (no angular gauge capability).
275
+ angle band is `?` (no angular gauge capability). A gauge or DRO resolution must be a
276
+ positive length or `"unknown"`: 0 or less is refused at load, never read as perfect
277
+ resolution ([inventory](docs/inventory.md)).
271
278
 
272
279
  Native [saw cut-off](docs/plan.md#saw-cut-off) supports `saw_cut` / `cut_off` on
273
280
  mill, bench and bandsaw setups: an explicit blade-centre plane plus inventory
274
281
  kerf removes only the discarded stock side. The traveler shows cited blade sfm
275
282
  and descent feed, not spindle RPM; cylinder-based accessibility explicitly does
276
- not apply. Dividing-head headroom uses centre height and the posed axis offset,
283
+ not apply. A [hand saw on the bench](docs/plan.md#hand-saw-on-the-bench-straight-or-to-a-scribed-line)
284
+ (`kind = "hand_saw"` on a bench/manual machine) keeps the manual-bench waiver and
285
+ has no blade speed; it cuts straight on a `cut_plane` or `follows` an earlier
286
+ scribe op's line, removing only its declared `stock_removal_bounds` and leaving
287
+ at least `allowance_mm` outside the line for the file (unknown extent is `?`). A
288
+ bandsaw in vertical mode (`saw_modes`) follows a scribed curve the same way, hand-fed;
289
+ along a scribed arc the predicted removal follows the curve, offset by the allowance. Dividing-head headroom uses centre height and the posed axis offset,
277
290
  and child hole operations inherit a missing travel centre from their named
278
291
  parent/hole while explicit unknown locations remain debt.
279
292
 
293
+ A cut that frees the part from its frame leaves a held split: every pressed piece
294
+ stays in the stock. The op that ends it (usually `release`) takes
295
+ `keep = "<feature>"`, and only the piece carrying that finished feature goes on to
296
+ later setups. Each discarded piece is logged by the
297
+ [`keep_piece`](docs/rules-geometry.md#keep-piece) finding. A keep that cannot pick
298
+ exactly one piece is `?`.
299
+
280
300
  On the lathe, a parting/grooving blade's Z touch names the blade corner it sets
281
301
  (the touched face's normal decides it; a scribe touch states it), and every blade
282
302
  op row prints Z as that corner's reading. Each toolpost tool's first touch-off in
@@ -409,7 +429,17 @@ phase: [#213](https://github.com/pedropaulovc/prechips/issues/213)); a
409
429
  arc's bore (or a template) and checks R with a radius gauge whose range covers
410
430
  it. `chords` (sagitta proven inside the band, ends on the DRO grid) and a
411
431
  `rotary_table` recipe (inventory table, centre pin, offset and dial readings)
412
- finish arcs on the mill. No traveler prints MDI, G-code or a continuous circle;
432
+ finish arcs on the mill, and a `guided_sweep` (#255), in a setup of its own, finishes an arc whose centre
433
+ is too far off the part for a table: the cutter is touched to a measured master
434
+ and locked while a swing arm on a pivot, or a sled whose rail bears on two
435
+ rollers, sweeps the work under it; the master's radius limits widened by the
436
+ measured pivot play, or the rail's form error and roller runout through the
437
+ rollers' geometry, must hold the band, and an unmeasured guide is debt, never a
438
+ perfect rail. The setup picture draws an arc sled's table-side rollers
439
+ render-only from the sled's measured `roller_dia`, `roller_height` and
440
+ `bed_height` and its `rail_side` (a missing fact is a `NOT SHOWN` note with its
441
+ `measure:` instruction), and notes where a swing arm's pivot post stands, off
442
+ the picture. No traveler prints MDI, G-code or a continuous circle;
413
443
  the former `arc_table` method and machine `contouring` are gone.
414
444
  Drawing dimensions retain their declared precision; when no precision is
415
445
  declared, known values still print their own digits rather than `?`. Operative
@@ -600,7 +630,15 @@ distinct from requirement-associated records: one full-width white freeform
600
630
  readings/observations area below each real requirement, at least 20 mm clear
601
631
  interior depth excluding labels, padding and borders. Known owners, limits,
602
632
  gauges, units and method references stay associated; feature/location can be
603
- identified when needed without inferred reading counts or statistics.
633
+ identified when needed without inferred reading counts or statistics. A
634
+ multi-feature `inspect` op prints as one station card; its `checks`,
635
+ `go_no_go` and `inspection_methods` may be scoped per listed feature
636
+ ([plan](docs/plan.md#operation)), and each feature's row then names its own
637
+ gauge, GO / NO-GO pair and method.
638
+ A radius band `[0, r]` ("R r MAX", sharp allowed) is read as a limit check: a
639
+ radius or profile gauge passes only with a leaf at exactly r and no declared leaf
640
+ tolerance. A safe smaller leaf the traveler does not name is
641
+ [`?`](docs/rules-inspection.md).
604
642
  Specifically named procedure blanks remain separate, with a 35 × 10 mm clear
605
643
  interior floor. Pre-clamping M acquisition occurs only for explicit
606
644
  `measure_before_hold = true` with an authored `measure`; pen room stays in its
@@ -632,6 +670,13 @@ When all component envelopes are known, their union must contain the finished
632
670
  STEP; otherwise all component references carry geometry debt.
633
671
  A nonempty root `as_is_faces` declaration needs the full joined supply exterior
634
672
  and every component envelope known, so it can withhold otherwise-known branches.
673
+ A blank bought already laser-, waterjet- or plasma-cut is a
674
+ [profiled blank](docs/plan.md#profiled-blank): `form = "profiled_blank"` with a
675
+ `blank` table naming its cited, digest-bound STEP (in part-model coordinates)
676
+ and the vendor's ± cut tolerance, on the root stock or a component. The kernel
677
+ uses that solid as the supply; [`blank_credit`](docs/rules-geometry.md#blank_credit)
678
+ credits an as-is face only when it lies on the blank and its drawing bands hold
679
+ the tolerance, so out-of-band faces still need ops.
635
680
  Empty or omitted declarations add no cross-component dependency. Removal
636
681
  fragmentation is checked per input solid; deleting one piece cannot mask
637
682
  splitting another.
@@ -652,12 +697,22 @@ over the face's full area. Declared fit bands must cover the
652
697
  worst-case mating diameters, prepared geometry must reach the join along the
653
698
  selected stock lineage, and the kernel refuses interference outside the
654
699
  permitted fit or missing component-owned finished material at the join.
655
- Unknown joint dimensions or retaining-compound process facts withhold the union
700
+ A joint's `forms` names exported features whose faces the join itself leaves, such as
701
+ a root fillet `R0.2 MAX` that a press leaves sharp. The kernel admits that sharp
702
+ corner against the feature's band, judged with the socket's declared mouth
703
+ `edge_break` (a broken mouth leaves a groove, which is refused), and `coverage` credits its faces only after a
704
+ post-join check reads the band. Without that check the credit is `?`.
705
+ A cylindrical joint's `method` is the assembly action (`silver_braze` or `slip`
706
+ for clearance, `press` for interference); a retaining compound on the interface
707
+ is a `compound = { product, cure_time_min, surface_prep }` block, required on a
708
+ slip fit and optional on a press fit.
709
+ Unknown joint dimensions or compound process facts withhold the union
656
710
  and its render, rather than assuming a fit or cure. Each assembly step joins
657
711
  exactly two disjoint inputs: an existing assembly may receive one new component.
658
712
  Finite both-open cylindrical sockets accept turned sleeves; clearance joining
659
- fills the mating annulus, not a sleeve's intentional bore. Retaining-compound
660
- travelers state surface prep, cure time and do not disturb until cured.
713
+ fills the mating annulus, not a sleeve's intentional bore. Compound-joint
714
+ travelers state surface prep, cure time and do not disturb until cured; a press
715
+ fit with a compound is pressed in with the compound on the interface.
661
716
  Plan-owned `process_features` describe stock preparation a later setup relies
662
717
  on — a faced `end_face` or a combined drill and countersink `centre_hole` — and
663
718
  are never drawing dimensions or coverage. The kernel cuts them, so the picture
@@ -672,6 +727,11 @@ unresolved. A tailstock drill chuck or a rest is not a centre. The centre depth
672
727
  is the Machinery's Handbook Table 6 drill length plus the countersink to the mouth,
673
728
  printed as a tailstock-quill depth, and only for the selected centre drill's
674
729
  own accepted D, C, angle, body and point with the mouth on the touched end.
730
+ A `tooling_hole` is a locating hole drilled (and reamed) in the work's own waste:
731
+ its ops are judged as plain hole ops, the kernel cuts it, a later setup's fixture
732
+ or clamp pin whose `locates` names it gets a band fit check, and a setup that
733
+ still names it after a waste cut took it is an error (`tooling_hole`, always
734
+ required where a setup works or locates on one).
675
735
  A mill route squares a sawn rectangular bar the same way: each blank face is
676
736
  an `end_face` that a face op cuts flat, or a side-milling profile cuts on an end
677
737
  overhanging the vise. The kernel removes each as a planar slab, so later pictures
@@ -681,6 +741,10 @@ declaration within the declared tolerance and requires an inventory gauge and
681
741
  written method for its size, flatness, squareness and parallelism checks, which
682
742
  the sheet prints as CHECK THE BLANK. A hold's `jaw_bar` puts a round bar against
683
743
  the moving jaw while a reference face seats on the fixed jaw.
744
+ A lathe `part_off` also prepares an `end_face`: a piece parted off over length
745
+ keeps its later stock known until a later setup faces it to length.
746
+ `parting_face` checks that the end face is the disc at the op's `to_z`, with its
747
+ axis pointing into the kept piece toward the exposed end.
684
748
  See [the plan format](docs/plan.md) for the authored fields.
685
749
 
686
750
  Cutter self-contact exclusion is only a thin shell of the sampled face;
@@ -45,8 +45,9 @@ shows ample real land: at least 2.4596 mm (about 2.46 mm), versus the 0.5 mm
45
45
  policy. Missing circular-topology proof, not physical land, keeps S8/S9's
46
46
  post-plunge retouches unknown pending [#208](https://github.com/pedropaulovc/prechips/issues/208).
47
47
  Release 0.1.1 includes #198, #192 and #199 with that documented interim exit 4.
48
- Release 0.1.2 adds #235, #236 and surface-profile zones and keeps that exit 4:
49
- exit-0 restoration by #208 is deferred to a later release;
48
+ Release 0.1.2 adds #235, #236 and surface-profile zones and keeps that exit 4.
49
+ Release 0.2.0 breaks input schemas without compatibility shims and still keeps
50
+ that exit 4: exit-0 restoration by #208 is deferred to a later release;
50
51
  the authored plan and its requirements are unchanged.
51
52
 
52
53
  See the example guide's [print and native acceptance limits](examples/README.md#print-and-native-acceptance-limits).
@@ -63,6 +64,10 @@ uv run ruff check .
63
64
  uv run ruff format --check .
64
65
  ```
65
66
 
67
+ While iterating, `uv run scripts/affected_tests.py --base main` runs only the
68
+ tests a change can affect (`--dry-run` prints the selection); CI always runs the
69
+ full suite.
70
+
66
71
  Install [FreeCAD 1.x](https://github.com/FreeCAD/FreeCAD/releases) for real
67
72
  geometry checks. Discovery uses `FREECAD_CMD` first, the installed Windows
68
73
  FreeCAD 1.1 path next, then `FreeCADCmd`/`freecadcmd` on `PATH`. For example,
@@ -80,9 +85,9 @@ two pytest workers and load-scope scheduling, caps extra native workers at one,
80
85
  records durations (complete in JUnit) and uploads the pytest log and JUnit
81
86
  results even on failure. Successful geometry results persist across runs in a
82
87
  cache namespaced by runner OS/architecture, pinned AppImage SHA-256, cache epoch
83
- and engine source digest. Each immutable save key also includes the run ID and
84
- attempt; restore uses the matching namespace prefix. Only successful test jobs
85
- publish the per-run cache.
88
+ and engine source digest (only the sources `freecadcmd` executes). Each immutable save key also includes the shard index,
89
+ run ID and attempt; a shard restores its own latest entry, else the shared
90
+ namespace prefix. Only successful test shards publish their per-run cache.
86
91
  The product reports unknown geometry (`?`, exit 4) without FreeCAD;
87
92
  skips do not change that behavior.
88
93
  CI also sets `PRECHIPS_REQUIRE_PRINT_BROWSER=1`: a missing or unlaunchable
@@ -247,16 +252,31 @@ only when it is read across a face: a drawn band whose feature names the face
247
252
  extent in `tilt_across` ([features](docs/features.md#angle-bands-read-across-a-face)),
248
253
  or a BASIC angle a surface profile bounds by `tilt_across`. Inspection judges it as
249
254
  the linear zone it reads across that length, on gauge resolution alone; any other
250
- angle band is `?` (no angular gauge capability).
255
+ angle band is `?` (no angular gauge capability). A gauge or DRO resolution must be a
256
+ positive length or `"unknown"`: 0 or less is refused at load, never read as perfect
257
+ resolution ([inventory](docs/inventory.md)).
251
258
 
252
259
  Native [saw cut-off](docs/plan.md#saw-cut-off) supports `saw_cut` / `cut_off` on
253
260
  mill, bench and bandsaw setups: an explicit blade-centre plane plus inventory
254
261
  kerf removes only the discarded stock side. The traveler shows cited blade sfm
255
262
  and descent feed, not spindle RPM; cylinder-based accessibility explicitly does
256
- not apply. Dividing-head headroom uses centre height and the posed axis offset,
263
+ not apply. A [hand saw on the bench](docs/plan.md#hand-saw-on-the-bench-straight-or-to-a-scribed-line)
264
+ (`kind = "hand_saw"` on a bench/manual machine) keeps the manual-bench waiver and
265
+ has no blade speed; it cuts straight on a `cut_plane` or `follows` an earlier
266
+ scribe op's line, removing only its declared `stock_removal_bounds` and leaving
267
+ at least `allowance_mm` outside the line for the file (unknown extent is `?`). A
268
+ bandsaw in vertical mode (`saw_modes`) follows a scribed curve the same way, hand-fed;
269
+ along a scribed arc the predicted removal follows the curve, offset by the allowance. Dividing-head headroom uses centre height and the posed axis offset,
257
270
  and child hole operations inherit a missing travel centre from their named
258
271
  parent/hole while explicit unknown locations remain debt.
259
272
 
273
+ A cut that frees the part from its frame leaves a held split: every pressed piece
274
+ stays in the stock. The op that ends it (usually `release`) takes
275
+ `keep = "<feature>"`, and only the piece carrying that finished feature goes on to
276
+ later setups. Each discarded piece is logged by the
277
+ [`keep_piece`](docs/rules-geometry.md#keep-piece) finding. A keep that cannot pick
278
+ exactly one piece is `?`.
279
+
260
280
  On the lathe, a parting/grooving blade's Z touch names the blade corner it sets
261
281
  (the touched face's normal decides it; a scribe touch states it), and every blade
262
282
  op row prints Z as that corner's reading. Each toolpost tool's first touch-off in
@@ -389,7 +409,17 @@ phase: [#213](https://github.com/pedropaulovc/prechips/issues/213)); a
389
409
  arc's bore (or a template) and checks R with a radius gauge whose range covers
390
410
  it. `chords` (sagitta proven inside the band, ends on the DRO grid) and a
391
411
  `rotary_table` recipe (inventory table, centre pin, offset and dial readings)
392
- finish arcs on the mill. No traveler prints MDI, G-code or a continuous circle;
412
+ finish arcs on the mill, and a `guided_sweep` (#255), in a setup of its own, finishes an arc whose centre
413
+ is too far off the part for a table: the cutter is touched to a measured master
414
+ and locked while a swing arm on a pivot, or a sled whose rail bears on two
415
+ rollers, sweeps the work under it; the master's radius limits widened by the
416
+ measured pivot play, or the rail's form error and roller runout through the
417
+ rollers' geometry, must hold the band, and an unmeasured guide is debt, never a
418
+ perfect rail. The setup picture draws an arc sled's table-side rollers
419
+ render-only from the sled's measured `roller_dia`, `roller_height` and
420
+ `bed_height` and its `rail_side` (a missing fact is a `NOT SHOWN` note with its
421
+ `measure:` instruction), and notes where a swing arm's pivot post stands, off
422
+ the picture. No traveler prints MDI, G-code or a continuous circle;
393
423
  the former `arc_table` method and machine `contouring` are gone.
394
424
  Drawing dimensions retain their declared precision; when no precision is
395
425
  declared, known values still print their own digits rather than `?`. Operative
@@ -580,7 +610,15 @@ distinct from requirement-associated records: one full-width white freeform
580
610
  readings/observations area below each real requirement, at least 20 mm clear
581
611
  interior depth excluding labels, padding and borders. Known owners, limits,
582
612
  gauges, units and method references stay associated; feature/location can be
583
- identified when needed without inferred reading counts or statistics.
613
+ identified when needed without inferred reading counts or statistics. A
614
+ multi-feature `inspect` op prints as one station card; its `checks`,
615
+ `go_no_go` and `inspection_methods` may be scoped per listed feature
616
+ ([plan](docs/plan.md#operation)), and each feature's row then names its own
617
+ gauge, GO / NO-GO pair and method.
618
+ A radius band `[0, r]` ("R r MAX", sharp allowed) is read as a limit check: a
619
+ radius or profile gauge passes only with a leaf at exactly r and no declared leaf
620
+ tolerance. A safe smaller leaf the traveler does not name is
621
+ [`?`](docs/rules-inspection.md).
584
622
  Specifically named procedure blanks remain separate, with a 35 × 10 mm clear
585
623
  interior floor. Pre-clamping M acquisition occurs only for explicit
586
624
  `measure_before_hold = true` with an authored `measure`; pen room stays in its
@@ -612,6 +650,13 @@ When all component envelopes are known, their union must contain the finished
612
650
  STEP; otherwise all component references carry geometry debt.
613
651
  A nonempty root `as_is_faces` declaration needs the full joined supply exterior
614
652
  and every component envelope known, so it can withhold otherwise-known branches.
653
+ A blank bought already laser-, waterjet- or plasma-cut is a
654
+ [profiled blank](docs/plan.md#profiled-blank): `form = "profiled_blank"` with a
655
+ `blank` table naming its cited, digest-bound STEP (in part-model coordinates)
656
+ and the vendor's ± cut tolerance, on the root stock or a component. The kernel
657
+ uses that solid as the supply; [`blank_credit`](docs/rules-geometry.md#blank_credit)
658
+ credits an as-is face only when it lies on the blank and its drawing bands hold
659
+ the tolerance, so out-of-band faces still need ops.
615
660
  Empty or omitted declarations add no cross-component dependency. Removal
616
661
  fragmentation is checked per input solid; deleting one piece cannot mask
617
662
  splitting another.
@@ -632,12 +677,22 @@ over the face's full area. Declared fit bands must cover the
632
677
  worst-case mating diameters, prepared geometry must reach the join along the
633
678
  selected stock lineage, and the kernel refuses interference outside the
634
679
  permitted fit or missing component-owned finished material at the join.
635
- Unknown joint dimensions or retaining-compound process facts withhold the union
680
+ A joint's `forms` names exported features whose faces the join itself leaves, such as
681
+ a root fillet `R0.2 MAX` that a press leaves sharp. The kernel admits that sharp
682
+ corner against the feature's band, judged with the socket's declared mouth
683
+ `edge_break` (a broken mouth leaves a groove, which is refused), and `coverage` credits its faces only after a
684
+ post-join check reads the band. Without that check the credit is `?`.
685
+ A cylindrical joint's `method` is the assembly action (`silver_braze` or `slip`
686
+ for clearance, `press` for interference); a retaining compound on the interface
687
+ is a `compound = { product, cure_time_min, surface_prep }` block, required on a
688
+ slip fit and optional on a press fit.
689
+ Unknown joint dimensions or compound process facts withhold the union
636
690
  and its render, rather than assuming a fit or cure. Each assembly step joins
637
691
  exactly two disjoint inputs: an existing assembly may receive one new component.
638
692
  Finite both-open cylindrical sockets accept turned sleeves; clearance joining
639
- fills the mating annulus, not a sleeve's intentional bore. Retaining-compound
640
- travelers state surface prep, cure time and do not disturb until cured.
693
+ fills the mating annulus, not a sleeve's intentional bore. Compound-joint
694
+ travelers state surface prep, cure time and do not disturb until cured; a press
695
+ fit with a compound is pressed in with the compound on the interface.
641
696
  Plan-owned `process_features` describe stock preparation a later setup relies
642
697
  on — a faced `end_face` or a combined drill and countersink `centre_hole` — and
643
698
  are never drawing dimensions or coverage. The kernel cuts them, so the picture
@@ -652,6 +707,11 @@ unresolved. A tailstock drill chuck or a rest is not a centre. The centre depth
652
707
  is the Machinery's Handbook Table 6 drill length plus the countersink to the mouth,
653
708
  printed as a tailstock-quill depth, and only for the selected centre drill's
654
709
  own accepted D, C, angle, body and point with the mouth on the touched end.
710
+ A `tooling_hole` is a locating hole drilled (and reamed) in the work's own waste:
711
+ its ops are judged as plain hole ops, the kernel cuts it, a later setup's fixture
712
+ or clamp pin whose `locates` names it gets a band fit check, and a setup that
713
+ still names it after a waste cut took it is an error (`tooling_hole`, always
714
+ required where a setup works or locates on one).
655
715
  A mill route squares a sawn rectangular bar the same way: each blank face is
656
716
  an `end_face` that a face op cuts flat, or a side-milling profile cuts on an end
657
717
  overhanging the vise. The kernel removes each as a planar slab, so later pictures
@@ -661,6 +721,10 @@ declaration within the declared tolerance and requires an inventory gauge and
661
721
  written method for its size, flatness, squareness and parallelism checks, which
662
722
  the sheet prints as CHECK THE BLANK. A hold's `jaw_bar` puts a round bar against
663
723
  the moving jaw while a reference face seats on the fixed jaw.
724
+ A lathe `part_off` also prepares an `end_face`: a piece parted off over length
725
+ keeps its later stock known until a later setup faces it to length.
726
+ `parting_face` checks that the end face is the disc at the op's `to_z`, with its
727
+ axis pointing into the kept piece toward the exposed end.
664
728
  See [the plan format](docs/plan.md) for the authored fields.
665
729
 
666
730
  Cutter self-contact exclusion is only a thin shell of the sampled face;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prechips"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "Checks before chips: deterministic validation of machining process plans against part geometry and a shop inventory."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -34,6 +34,8 @@ dev = [
34
34
  "pillow>=12.3.0",
35
35
  "pypdfium2>=5.14.0",
36
36
  "pytest>=8.3",
37
+ "pytest-split>=0.11.0",
38
+ "pytest-testmon>=2.2.0",
37
39
  "pytest-xdist>=3.8.0",
38
40
  "ruff>=0.8",
39
41
  ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "prechips"
3
- version = "0.1.2"
3
+ version = "0.2.0"
4
4
  description = "Checks before chips: deterministic validation of machining process plans against part geometry and a shop inventory."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -31,6 +31,8 @@ dev = [
31
31
  "pillow>=12.3.0",
32
32
  "pypdfium2>=5.14.0",
33
33
  "pytest>=8.3",
34
+ "pytest-split>=0.11.0",
35
+ "pytest-testmon>=2.2.0",
34
36
  "pytest-xdist>=3.8.0",
35
37
  "ruff>=0.8",
36
38
  ]
@@ -1,3 +1,3 @@
1
1
  """prechips: checks before chips."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.2.0"
@@ -11,11 +11,13 @@ import re
11
11
  import sys
12
12
  import tomllib
13
13
  import uuid
14
+ from copy import deepcopy
14
15
  from pathlib import Path
15
16
 
16
17
  from prechips import __version__, telemetry
17
18
  from prechips.findings import Finding, exit_code
18
19
  from prechips.inputs import BadInput, Bundle, load_bundle, load_inventory
20
+ from prechips.model import profiled
19
21
  from prechips.report import (
20
22
  build_report,
21
23
  canonical_bytes,
@@ -140,12 +142,15 @@ def _write_outputs(
140
142
  temporary.unlink(missing_ok=True)
141
143
 
142
144
 
143
- def _evaluate(bundle: Bundle, tracing: telemetry.Telemetry):
145
+ def _evaluate(bundle: Bundle, job: dict, tracing: telemetry.Telemetry):
146
+ """Every rule's findings on ``bundle``. Each geometry rule reads the bundle's kernel
147
+ job (prepared_job): ``job``, built once by the caller, is lent to each rule as a copy."""
144
148
  from prechips.rules import RULES, required_coverage
149
+ from prechips.rules.geometry_common import lend_job
145
150
 
146
151
  findings = []
147
152
  for rule in RULES:
148
- with tracing.span(f"rule.{rule.name}.evaluate"):
153
+ with tracing.span(f"rule.{rule.name}.evaluate"), lend_job(bundle, job):
149
154
  rows = rule.evaluate(bundle)
150
155
  for finding in rows:
151
156
  with tracing.span(
@@ -516,6 +521,8 @@ def _stock_piece_volume(piece: dict, subject: str) -> dict:
516
521
  if round_form
517
522
  else "section_mm, length_mm"
518
523
  if rectangular_form
524
+ else "blank STEP (a profiled blank's volume is not compared)"
525
+ if profiled(piece)
519
526
  else "form, length_mm (shape unresolved)"
520
527
  )
521
528
  return {
@@ -694,10 +701,16 @@ def _run(args, tracing: telemetry.Telemetry) -> int:
694
701
  destinations = _output_paths(out, names, all_inputs)
695
702
  except OSError as exc:
696
703
  raise BadInput(f"Cannot check output path: {exc}") from exc
697
- from prechips.kernel import run_geometries
698
-
699
- run_geometries(bundles)
700
- findings = [_evaluate(bundle, tracing) for bundle in bundles]
704
+ from prechips.kernel import build_job, run_geometries
705
+
706
+ # One kernel job per bundle serves both the kernel request and every rule: the bundle
707
+ # does not change in between, and the job does not read the kernel's result.
708
+ jobs = []
709
+ for bundle in bundles:
710
+ with tracing.span("kernel.build_job"):
711
+ jobs.append(build_job(bundle))
712
+ run_geometries(bundles, [deepcopy(job) for job in jobs])
713
+ findings = [_evaluate(bundle, job, tracing) for bundle, job in zip(bundles, jobs, strict=True)]
701
714
  try:
702
715
  assets = [render_assets(bundle) for bundle in bundles]
703
716
  reports = [
@@ -47,16 +47,19 @@ class Finding:
47
47
  # hand-finished arc needs, a centre the work rides on must be one the stock already
48
48
  # has, and prepared_blank gates the size of the blank a declared preparation hands the
49
49
  # first machining setup; bought tooling no receipt check can accept leaves the setup
50
- # nothing proven to hold or guide the work with: no policy omission can waive them. A
51
- # not_applicable row never blocks.
50
+ # nothing proven to hold or guide the work with; a keep that cannot pick the piece that
51
+ # leaves a held split leaves every later setup's stock unknown: no policy omission can
52
+ # waive them. A not_applicable row never blocks.
52
53
  ALWAYS_REQUIRED = frozenset(
53
54
  {
54
55
  "joint_fit",
55
56
  "joint_assembly",
56
57
  "manual_arc",
57
58
  "centre_support",
59
+ "tooling_hole",
58
60
  "prepared_blank",
59
61
  "purchased_tooling",
62
+ "keep_piece",
60
63
  }
61
64
  )
62
65