@structupath/pi-steel 0.2.1 → 0.2.3
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.
- package/DATA_PROVENANCE.json +23 -0
- package/DATA_PROVENANCE.md +35 -0
- package/PUBLIC_DATA_POLICY.md +52 -0
- package/README.md +114 -33
- package/docs/assets/pi-steel-demo.gif +0 -0
- package/docs/assets/pi-steel-gallery.webp +0 -0
- package/package.json +35 -4
- package/pyproject.toml +28 -0
- package/requirements-dev.txt +5 -0
- package/requirements-render.txt +1 -0
- package/requirements-tested.txt +9 -0
- package/requirements.txt +7 -0
- package/scripts/check-data-provenance.py +140 -0
- package/scripts/check-public-data.py +401 -0
- package/scripts/doctor.py +127 -0
- package/skills/_shared/bootstrap.py +32 -0
- package/skills/_shared/pi_steel/__init__.py +47 -0
- package/skills/_shared/pi_steel/cli.py +167 -0
- package/skills/_shared/pi_steel/contracts.py +87 -0
- package/skills/_shared/pi_steel/geometry_verify.py +243 -0
- package/skills/_shared/pi_steel/parsing.py +205 -0
- package/skills/_shared/pi_steel/run_manifest.py +317 -0
- package/skills/_shared/pi_steel/validation.py +597 -0
- package/skills/_shared/schemas/estimate-package.schema.json +424 -0
- package/skills/_shared/schemas/nest-result.schema.json +443 -0
- package/skills/_shared/schemas/run-manifest.schema.json +145 -0
- package/skills/steel-estimate/SKILL.md +119 -0
- package/skills/steel-estimate/references/estimate-package-example.json +91 -0
- package/skills/steel-estimate/references/output-contract.md +47 -0
- package/skills/steel-estimate/scripts/acknowledge-finding.py +193 -0
- package/skills/steel-estimate/scripts/build-estimate-package.py +836 -0
- package/skills/steel-nest/SKILL.md +50 -23
- package/skills/steel-nest/references/FIXTURE_PROVENANCE.md +12 -0
- package/skills/steel-nest/references/example_job.json +21 -25
- package/skills/steel-nest/references/job_template.json +11 -9
- package/skills/steel-nest/scripts/nest.py +1276 -250
- package/skills/steel-rfq/SKILL.md +93 -175
- package/skills/steel-rfq/assets/company-profile.example.json +11 -5
- package/skills/steel-rfq/references/rfq-input.md +58 -0
- package/skills/steel-rfq/scripts/generate-rfq.py +1221 -0
- package/skills/steel-rfq/scripts/recalc.py +33 -10
- package/skills/steel-takeoff/SKILL.md +12 -8
- package/skills/steel-takeoff/assets/bom-template.csv +1 -1
- package/skills/steel-takeoff/references/takeoff-procedures.md +3 -1
- package/skills/steel-takeoff/scripts/calculate-weight.sh +5 -10
- package/skills/steel-takeoff/scripts/lookup-member.sh +2 -2
- package/skills/steel-takeoff/scripts/validate-bom.py +151 -196
- package/skills/steel-rfq/scripts/__pycache__/recalc.cpython-312.pyc +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: steel-nest
|
|
3
|
-
description: "Nest steel parts onto stock plates and estimate material — the plate-layout / cutting step that CAM software
|
|
3
|
+
description: "Nest steel parts onto compatible stock plates and estimate material — the plate-layout / cutting step that CAM software does. Use this skill whenever someone mentions nesting, plate layout, plate optimization, cut list, cutting plan, yield, remnant candidates, how many sheets/plates a job needs, how much plate to buy, or laying parts out on a sheet. Produces a verified result, packing utilization, net material yield, guarded layouts, and optional cost totals only when an explicit basis exists."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Steel Plate Nesting & Estimate
|
|
7
7
|
|
|
8
8
|
## What This Skill Does
|
|
9
9
|
|
|
10
|
-
This is
|
|
10
|
+
This is an estimating-oriented version of the plate-nesting step performed by CAM software. It validates and separates material/grade/thickness groups, packs them onto compatible stock, independently verifies placements, and reports two different metrics: bounding-footprint packing utilization and net material yield. Leftover rectangles are remnant candidates, not certified reusable stock.
|
|
11
11
|
|
|
12
|
-
It exists so
|
|
12
|
+
It exists so an estimator can get a fast, repeatable material number and a reviewable layout. It does not replace CAM setup or operator verification.
|
|
13
13
|
|
|
14
14
|
## What It Does Well vs. What It Doesn't
|
|
15
15
|
|
|
@@ -17,18 +17,22 @@ Be honest with the user about the boundary — it protects the shop from over-tr
|
|
|
17
17
|
|
|
18
18
|
**Reliable:**
|
|
19
19
|
- Rectangular / plate-blank parts nest **exactly** (MaxRects bin-packing with rotation).
|
|
20
|
-
- Multiple plate sizes
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
20
|
+
- Multiple compatible plate sizes with independently verified bounds, non-overlap, and material grouping.
|
|
21
|
+
- Explicit clearance ownership: edge margin is the plate-to-part keep-out; kerf plus part gap is the minimum edge-to-edge clearance between parts. No trailing kerf/gap is required at the usable plate boundary.
|
|
22
|
+
- **Holes and rectangular cutouts** on verified rectangular parts — subtracted from weight/cost, rotated with the part, and emitted as cut geometry only when the complete job passes the output gate.
|
|
23
|
+
- Separate packing-utilization and net-material-yield percentages with approximation labels.
|
|
24
|
+
- Remnant candidates per plate, explicitly not certified reusable drops.
|
|
25
|
+
- Material weight and optional cost by one explicit basis per stock entry (`cost_per_lb` or `cost_per_sheet`, never both).
|
|
24
26
|
- Labeled layout (PDF + one PNG per plate).
|
|
25
|
-
- **
|
|
27
|
+
- **Reference files**: `reference_nest.dxf` plus `reference_plate_N.dxf`, with sheet outlines, bounding boxes, holes, and labels for estimating review.
|
|
28
|
+
- **Guarded cut-geometry files**: one DXF per sheet (`burn_plate_N.dxf`) containing only closed part outlines on `PROFILE` and holes/cutouts on `HOLES`, with origin at the sheet corner. They exist only when every part is rectangular, every required part fits, and every supported hole stays inside its part.
|
|
26
29
|
|
|
27
30
|
**Approximate — always flag it:**
|
|
28
31
|
- **Irregular parts** (gussets, brackets, curved profiles, parts with holes) are nested by their **bounding box**, not true shape. Real yield is a little better than reported. For exact weight/cost on those, get the true cut area (in²) into the part's `area` field. This is NOT true-shape nesting like a dedicated CAM engine.
|
|
32
|
+
- Any irregular part suppresses all fabrication-style DXFs for that job. The remaining PDF, PNG, report, JSON, and `reference_nest.dxf` outputs are estimating aids, not cutting instructions.
|
|
29
33
|
|
|
30
34
|
**Do NOT pretend to do:**
|
|
31
|
-
- **Machine-ready G-code / NC** with kerf compensation, pierce points, and lead-ins for a specific controller. That is machine-specific and safety-critical and must come from the real post-processor.
|
|
35
|
+
- **Machine-ready G-code / NC** with kerf compensation, pierce points, and lead-ins for a specific controller. That is machine-specific and safety-critical and must come from the real post-processor. When a burn DXF is emitted, it is still an import geometry file that requires operator and CAM verification. Say so plainly if asked for G-code.
|
|
32
36
|
|
|
33
37
|
## Inputs to Gather
|
|
34
38
|
|
|
@@ -36,8 +40,8 @@ Everything drives a single job JSON (schema in `references/job_template.json`; a
|
|
|
36
40
|
|
|
37
41
|
Gather three things:
|
|
38
42
|
|
|
39
|
-
1. **Parts** — for each unique part: name, width × height (inches; use the bounding box for odd shapes), quantity, whether it's `rect` or `irregular`, and whether rotation is allowed (`rotatable: false` locks grain/rolling direction for anisotropic material or directional finish). If a part has **holes or cutouts
|
|
40
|
-
2. **Stock** — plate size(s)
|
|
43
|
+
1. **Parts** — for each unique part: name, width × height (inches; use the bounding box for odd shapes), quantity, whether it's `rect` or `irregular`, and whether rotation is allowed (`rotatable: false` locks grain/rolling direction for anisotropic material or directional finish). If a rectangular part has **holes or cutouts**, add a `holes` list — each hole's `x,y` is its center from the part's lower-left corner: round = `{"dia":, "x":, "y":}`, rectangular cutout = `{"w":, "h":, "x":, "y":}`. A supported hole must remain fully inside its part or fabrication-style DXFs are suppressed. Holes are optional; skip them if you only need the layout/estimate.
|
|
44
|
+
2. **Stock** — plate size(s), explicit material, grade, and thickness, plus finite quantity or `unlimited`. A price is optional; if provided, use exactly one approved basis (`cost_per_lb` or `cost_per_sheet`) and retain its source outside this legacy JSON boundary.
|
|
41
45
|
3. **Cut settings** — kerf, part gap, edge margin, material density. Sensible defaults are in the template; only ask if the user hasn't implied them. Common kerf: plasma ~0.06", oxy-fuel ~0.10", laser ~0.02", waterjet ~0.03".
|
|
42
46
|
|
|
43
47
|
If a spreadsheet is provided, read it with pandas first, map columns to the part fields, and confirm your interpretation before nesting. Do not silently guess quantities or dimensions.
|
|
@@ -55,33 +59,56 @@ Write the job JSON, then run the engine:
|
|
|
55
59
|
python3 scripts/nest.py --job <job.json> --out <outdir>
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
The legacy job JSON shape and command arguments remain accepted. Material, grade,
|
|
63
|
+
thickness, and imperial unit basis must now be explicit at job level or on each
|
|
64
|
+
part/stock entry; the engine never infers those facts from a display name. `<outdir>` is a
|
|
65
|
+
publication root rather than a flat artifact directory. Every invocation creates
|
|
66
|
+
`<outdir>/runs/<run-id>/` and atomically updates `<outdir>/latest-run.json`; follow
|
|
67
|
+
that pointer to find the current run. This prevents an older burn file from appearing
|
|
68
|
+
current after a blocked rerun.
|
|
69
|
+
|
|
70
|
+
Use `--geometry-verified-only` when reference-only geometry does not satisfy the
|
|
71
|
+
request. The command still publishes its QA diagnostics, but exits unsuccessfully.
|
|
72
|
+
|
|
73
|
+
Each run contains:
|
|
74
|
+
|
|
75
|
+
- `run-manifest.json` and `qa-report.json` — outcome, readiness, hashes, warnings, and the exact artifact allow-list
|
|
59
76
|
- `layout.pdf` — every plate drawn (holes shown) + a summary page (the main deliverable)
|
|
60
77
|
- `plate_1.png`, `plate_2.png`, … — one image per plate
|
|
61
|
-
- `burn_plate_1.dxf`, `burn_plate_2.dxf`, … —
|
|
62
|
-
- `
|
|
63
|
-
- `rfq_nesting.json` —
|
|
78
|
+
- `burn_plate_1.dxf`, `burn_plate_2.dxf`, … — geometry-verified cut entities only; absent for review-required or blocked runs
|
|
79
|
+
- `reference_nest.dxf` and `reference_plate_N.dxf` — explicitly reference-only layouts
|
|
80
|
+
- `rfq_nesting.json` — versioned `1.0.0` Material / Nesting Plan / Remnant Candidate rows for the `steel-rfq` hand-off; absent on blocked runs
|
|
64
81
|
- `report.txt` — the text report
|
|
65
|
-
- `result.json` —
|
|
82
|
+
- `result.json` — schema-versioned result with normalized input/configuration hashes, algorithm version, placements, verifier findings, both metrics, and cost status
|
|
83
|
+
|
|
84
|
+
Exit meanings:
|
|
85
|
+
|
|
86
|
+
- `0` — `ready`; requested artifacts were published and cut geometry is verified within the supported rectangular scope
|
|
87
|
+
- `2` — `review_required`; safe reference outputs were published, but no burn DXF exists
|
|
88
|
+
- `3` — `blocked`; validation or unplaced material prevented fabrication-style output
|
|
89
|
+
- `4` — a required runtime capability is missing
|
|
90
|
+
- `1` — usage or internal error
|
|
66
91
|
|
|
67
|
-
|
|
92
|
+
Install the declared dependencies from the package root with
|
|
93
|
+
`python3 -m pip install -r requirements-dev.txt`. Formula baking remains an optional
|
|
94
|
+
capability reported by `python3 scripts/doctor.py`.
|
|
68
95
|
|
|
69
96
|
## What to Deliver
|
|
70
97
|
|
|
71
|
-
Always deliver the **PDF layout** and give the headline numbers in the message: plates used,
|
|
98
|
+
Always deliver the **PDF layout** and give the headline numbers in the message: plates used, packing utilization, net material yield, cost status, and aggregated quantities that did not fit. Offer the reference DXF and per-plate PNGs. Offer burn DXFs only when they were emitted by the guard, and still state that CAM/operator verification is required.
|
|
72
99
|
|
|
73
|
-
Verify before presenting:
|
|
100
|
+
Verify before presenting: require `verification.status = verified`, reconcile known cost to its recorded per-sheet or per-pound basis, and never turn a missing or incomplete cost into `$0`.
|
|
74
101
|
|
|
75
102
|
## Integration with steel-rfq
|
|
76
103
|
|
|
77
|
-
The `steel-rfq` skill has a "Nesting / Drop Reference" table.
|
|
104
|
+
The `steel-rfq` skill has a "Nesting / Drop Reference" table. The engine writes the versioned object `{schema_version, source_nest_result_version, rows}`. Rows remain separate by stock identity, material, grade, thickness, and sheet size, even when display names match. Resolve the current run through `latest-run.json` and reject unknown handoff versions.
|
|
78
105
|
|
|
79
106
|
## Common Variations
|
|
80
107
|
|
|
81
|
-
**Mixed thickness / grade in one order** —
|
|
108
|
+
**Mixed thickness / grade in one order** — provide the basis on each part and stock entry. The engine creates independent compatible groups and never opens an incompatible plate.
|
|
82
109
|
|
|
83
|
-
**"Just tell me how many sheets"** — still run it; the plate count is the answer
|
|
110
|
+
**"Just tell me how many sheets"** — still run it; the plate count is the answer. Cost remains absent unless an explicit basis is supplied.
|
|
84
111
|
|
|
85
|
-
**Remnant
|
|
112
|
+
**Remnant reuse** — output rectangles are candidates only. Measure, identify, status, and approve a candidate as inventory before supplying it as a distinct stock entry in a later estimate.
|
|
86
113
|
|
|
87
114
|
**Grain / directional material** — set `rotatable: false` on those parts so the nester won't spin them 90°.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Fixture Provenance
|
|
2
|
+
|
|
3
|
+
`example_job.json` and `job_template.json` are synthetic public examples created
|
|
4
|
+
from scratch for pi-steel documentation and tests. They are not copied, transformed,
|
|
5
|
+
rounded, renamed, or anonymized from a company, customer, vendor, bid, drawing,
|
|
6
|
+
takeoff, inventory record, or production job.
|
|
7
|
+
|
|
8
|
+
- Creator: StructuPath pi-steel maintainers
|
|
9
|
+
- Creation method: deliberately invented geometry and identifiers
|
|
10
|
+
- Public-data review: 2026-07-28
|
|
11
|
+
- Commercial data: none
|
|
12
|
+
- Private source artifacts: none
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
|
-
"job_name": "
|
|
3
|
-
"customer": "
|
|
2
|
+
"job_name": "SYNTHETIC-DEMO-001 Generated Training Geometry",
|
|
3
|
+
"customer": "Example Customer",
|
|
4
|
+
"material": "carbon_steel",
|
|
5
|
+
"grade": "A36",
|
|
6
|
+
"unit_system": "imperial",
|
|
4
7
|
"settings": {
|
|
5
8
|
"kerf_in": 0.06,
|
|
6
9
|
"part_gap_in": 0.25,
|
|
@@ -10,38 +13,31 @@
|
|
|
10
13
|
},
|
|
11
14
|
"stock": [
|
|
12
15
|
{
|
|
13
|
-
"name": "
|
|
14
|
-
"width":
|
|
15
|
-
"height":
|
|
16
|
+
"name": "Synthetic Plate",
|
|
17
|
+
"width": 60,
|
|
18
|
+
"height": 30,
|
|
16
19
|
"thickness": 0.5,
|
|
17
|
-
"qty":
|
|
18
|
-
"cost_per_lb": 0.92
|
|
20
|
+
"qty": 3
|
|
19
21
|
}
|
|
20
22
|
],
|
|
21
23
|
"parts": [
|
|
22
|
-
{ "name": "
|
|
24
|
+
{ "source_id": "SYNTHETIC-SRC-RECT-A", "name": "Training Rect A", "width": 11, "height": 7, "qty": 3, "shape": "rect",
|
|
23
25
|
"holes": [
|
|
24
|
-
{ "dia": 0.
|
|
25
|
-
{ "dia": 0.
|
|
26
|
-
{ "dia": 0.875, "x": 2, "y": 8 },
|
|
27
|
-
{ "dia": 0.875, "x": 12, "y": 8 }
|
|
26
|
+
{ "dia": 0.75, "x": 2, "y": 2 },
|
|
27
|
+
{ "dia": 0.75, "x": 9, "y": 5 }
|
|
28
28
|
] },
|
|
29
|
-
{ "name": "
|
|
29
|
+
{ "source_id": "SYNTHETIC-SRC-RECT-B", "name": "Training Rect B", "width": 13, "height": 9, "qty": 2, "shape": "rect",
|
|
30
30
|
"holes": [
|
|
31
|
-
{ "dia":
|
|
32
|
-
{ "dia":
|
|
33
|
-
{ "dia": 1.0625, "x": 3, "y": 13 },
|
|
34
|
-
{ "dia": 1.0625, "x": 13, "y": 13 }
|
|
31
|
+
{ "dia": 0.625, "x": 3, "y": 3 },
|
|
32
|
+
{ "dia": 0.625, "x": 10, "y": 6 }
|
|
35
33
|
] },
|
|
36
|
-
{ "name": "
|
|
37
|
-
{ "name": "
|
|
38
|
-
{ "name": "
|
|
39
|
-
{ "name": "Cover CV1", "width": 24, "height": 18, "qty": 3, "shape": "rect",
|
|
34
|
+
{ "source_id": "SYNTHETIC-SRC-IRREGULAR-C", "name": "Training Irregular C", "width": 9, "height": 8, "qty": 4, "shape": "irregular", "area": 28 },
|
|
35
|
+
{ "source_id": "SYNTHETIC-SRC-RECT-D", "name": "Training Rect D", "width": 17, "height": 5, "qty": 5, "shape": "rect" },
|
|
36
|
+
{ "source_id": "SYNTHETIC-SRC-RECT-E", "name": "Training Rect E", "width": 19, "height": 11, "qty": 2, "shape": "rect",
|
|
40
37
|
"holes": [
|
|
41
|
-
{ "w":
|
|
38
|
+
{ "w": 5, "h": 3, "x": 9.5, "y": 5.5 }
|
|
42
39
|
] },
|
|
43
|
-
{ "name": "
|
|
44
|
-
"holes": [ { "dia": 0.
|
|
45
|
-
{ "name": "Web Plate WP1", "width": 40, "height": 12, "qty": 4, "shape": "rect" }
|
|
40
|
+
{ "source_id": "SYNTHETIC-SRC-RECT-F", "name": "Training Rect F", "width": 6, "height": 4, "qty": 7, "shape": "rect",
|
|
41
|
+
"holes": [ { "dia": 0.5, "x": 3, "y": 2 } ] }
|
|
46
42
|
]
|
|
47
43
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"job_name": "
|
|
3
|
-
"customer": "Customer
|
|
2
|
+
"job_name": "SYNTHETIC-DEMO-XXX Short description",
|
|
3
|
+
"customer": "Example Customer",
|
|
4
|
+
"material": "carbon_steel",
|
|
5
|
+
"grade": "A36",
|
|
6
|
+
"unit_system": "imperial",
|
|
4
7
|
|
|
5
|
-
"_comment_settings": "Cut/gap in inches. kerf
|
|
8
|
+
"_comment_settings": "Cut/gap in inches. kerf + part_gap is the minimum edge-to-edge clearance between parts. edge_margin alone is the plate-edge keep-out; no trailing kerf/gap is required at that boundary. density lb/in^3 must match the explicit material basis.",
|
|
6
9
|
"settings": {
|
|
7
10
|
"kerf_in": 0.06,
|
|
8
11
|
"part_gap_in": 0.25,
|
|
@@ -11,28 +14,27 @@
|
|
|
11
14
|
"density_lb_in3": 0.2836
|
|
12
15
|
},
|
|
13
16
|
|
|
14
|
-
"_comment_stock": "One entry per plate size
|
|
17
|
+
"_comment_stock": "One entry per distinct plate size/material/grade/thickness. qty = finite sheets available or set unlimited=true. Add either cost_per_lb or cost_per_sheet from an approved project input, never both; public templates intentionally contain no pricing.",
|
|
15
18
|
"stock": [
|
|
16
19
|
{
|
|
17
20
|
"name": "A36 Plate 1/2\"",
|
|
18
21
|
"width": 96,
|
|
19
22
|
"height": 48,
|
|
20
23
|
"thickness": 0.5,
|
|
21
|
-
"qty": 4
|
|
22
|
-
"cost_per_lb": 0.92
|
|
24
|
+
"qty": 4
|
|
23
25
|
}
|
|
24
26
|
],
|
|
25
27
|
|
|
26
28
|
"_comment_parts": "One entry per unique part. width/height in inches (bounding box for irregular parts). qty = pieces needed. rotatable=false to lock grain/rolling direction. shape: \"rect\" nests exactly; \"irregular\" nests by bounding box — add \"area\" (true cut area in^2) for exact weight/cost on those.",
|
|
27
29
|
"_comment_holes": "Optional per-part \"holes\": each hole's x,y is its CENTER measured from the part's lower-left corner. Round hole = {\"dia\": D, \"x\":, \"y\":}. Rectangular cutout = {\"w\":, \"h\":, \"x\":, \"y\":}. Holes are subtracted from weight/cost, rotate with the part, and are cut as real geometry on the HOLES layer of the burn-table DXF.",
|
|
28
30
|
"parts": [
|
|
29
|
-
{ "name": "Part A", "width": 12, "height": 8, "qty": 6, "shape": "rect",
|
|
31
|
+
{ "source_id": "SYNTHETIC-SRC-PART-A", "name": "Part A", "width": 12, "height": 8, "qty": 6, "shape": "rect",
|
|
30
32
|
"holes": [
|
|
31
33
|
{ "dia": 0.875, "x": 2, "y": 2 },
|
|
32
34
|
{ "dia": 0.875, "x": 10, "y": 6 }
|
|
33
35
|
] },
|
|
34
|
-
{ "name": "Part B", "width": 10, "height": 10, "qty": 4, "shape": "irregular", "area": 62 },
|
|
35
|
-
{ "name": "Part C", "width": 24, "height": 3, "qty": 10, "shape": "rect", "rotatable": false,
|
|
36
|
+
{ "source_id": "SYNTHETIC-SRC-PART-B", "name": "Part B", "width": 10, "height": 10, "qty": 4, "shape": "irregular", "area": 62 },
|
|
37
|
+
{ "source_id": "SYNTHETIC-SRC-PART-C", "name": "Part C", "width": 24, "height": 3, "qty": 10, "shape": "rect", "rotatable": false,
|
|
36
38
|
"holes": [ { "w": 3, "h": 1, "x": 12, "y": 1.5 } ] }
|
|
37
39
|
]
|
|
38
40
|
}
|